今回はVuexを完全に理解しました!(Xiaobaiレベルのチュートリアル)

一緒に書く習慣を身につけましょう!「ナゲッツデイリーニュープラン・4月アップデートチャレンジ」に参加して6日目です。クリックしてイベントの詳細を表示

序文

今日の記事のテーマはですVuexので、この記事を読む前に一定の基礎を持っていることが最善です。それをVue学んでいない場合Vueは、最初に読んでみることをお勧めしますVue.jsただし、フロントエンドパートナーの場合、Vueフレームワークは多かれ少なかれ公開されると思います。さて、これ以上面倒なことはせずに、要点を説明しましょう。

注:Vue-cliバージョンが異なれば、生成されるプロジェクトディレクトリとメソッドも異なる可能性があるため、それらについて心配する必要はありません。

1.それはなんVuexですか?

1.1公式説明

VuexVue.js これは、アプリケーション 用に特別に開発され た状態管理モードです。一元化されたストアを使用してアプリケーションのすべてのコンポーネントの状態を管理し、対応するルールを使用して状態が予測可能な方法で変化するようにします。

1.2個人の概要

上記の段落を読んだ後、ほとんどの人は混乱していると思います。公式の言葉は、権威と正確さを確保する必要があるため、通常はあいまいで理解しにくいものです。私自身の理解を通してあなたに簡単な理解を与えます:

いわゆる設計VuexVue.jsデータウェアハウスとは、各コンポーネントの共通データを統合管理用のウェアハウスに入れることで、非親コンポーネントと子コンポーネント間のデータ共有をシンプルかつ明確にするだけでなく、プログラムがより実行可能になります。メンテナンス(データの抽出)、およびウェアハウス内のデータが変更される限り、他のコンポーネントでデータが参照される場所も自動的に更新されます。

2.なぜそれを使うのVuexですか?

2.1コンポーネント間の複雑な値の転送

如果你之前用过Vue.js开发过项目,你一定会被各个组件之间的传值搞得晕头转向,特别是非父子组件之间传值时。利用Vuex我们可以将组件之间共享的数据抽取出来,单独存放在一个store(仓库)中去,这样各个组件需要数据的时候直接去仓库里面拿就好了,不用组件之间复杂的传值了,而且需要改变数据的时候,只需要将仓库里面的数据更改即可,各个组件里面引用的地方会自动更新。

2.2 Vue中的单项数据流

与单向数据流对应的就是双向数据流:双向数据流在Vue中也叫做‘双向绑定’,其实现主要是依靠MVVM框架,在Vue中主要由三个部分组成:ViewViewModelModel。其中View可以简单的理解为视图层,Model可以简单的理解为数据层,其中View与Model之间是不能直接通信的,必须得依靠ViewModel中间件来完成。通过ViewModel就可以实现数据双向绑定,也就是ViewModel之间的同步是自动的,Model数据改变了View视图上的数据也会跟着改变,而不必手动去更新。具体的原理在这里不是重点,就不细说了,感兴趣的小伙伴可以参考:

送上一张图:

Vue中的单向数据流:理解单项数据流,我们先来简单看一下官网给出的一张图:

我们先来简单理解一下图中三个层说明的什么:View(视图)、Actions(响应状态变化,可以简单理解为methods等等)、State(数据源,简单理解就是数据)。从图中的箭头我们可以看出,我们通过Actions改变State(数据),然后View(视图)更新,这一个过程是单向的,不会发生View(视图)改变了,然后State(数据)更新的情况,这使得我们的数据可控。我们再来看一下官网给出的实例代码,简单明了:

new Vue({
  // state
  data () {
    return {
      count: 0
    }
  },
  // view
  template: `
    <div>{{ count }}</div>
  `,
  // actions
  methods: {
    increment () {
      this.count++
    }
  }
})
复制代码

从上面代码中可以很清楚的反映出图片中的流程。在Vue中,数据从父组件传递给子组件,只能是单向绑定,子组件内部不能直接修改从父级传过来的数据,这样做的好处是所有状态变化都可以被记录、跟踪,状态变化通过手动调用通知,源头易追溯,没有“暗箱操作”(ViewModel)。但是,当我们遇到多个组件同时共享一个状态(数据)时,并且都需要改变状态(数据)时,单项数据的简洁性很容易被破坏,即使我们使用了父子组件的单向数据绑定,但这种模式在这种情况下也会变得非常脆弱,使代码不易维护。

总结出来就以下两个问题容易破坏单向数据流:

多个视图依赖于同一状态。
来自不同视图的行为需要变更同一状态。

因此,我们为什么不把组件的共享状态抽取出来,以一个全局单例模式管理呢?在这种模式下,我们的组件树构成了一个巨大的“视图”,不管在树的哪个位置,任何组件都能获取状态或者触发行为!
通过定义和隔离状态管理中的各种概念并通过强制规则维持视图和状态间的独立性,我们的代码将会变得更结构化且易维护。

3.什么情况下该使用Vuex?

Vuex 可以帮助我们管理共享状态,并附带了更多的概念和框架。这需要对短期和长期效益进行权衡。 如果您不打算开发大型单页应用,使用 Vuex 可能是繁琐冗余的。确实是如此——如果您的应用够简单,您最好不要使用 Vuex。一个简单的 store 模式就足够您所需了。但是,如果您需要构建一个中大型单页应用,您很可能会考虑如何更好地在组件外部管理状态,Vuex 将会成为自然而然的选择。\

4.构建项目和引入Vuex

前面为什么要花这么大力气去讲这么多基础知识呢?俗话说:‘万丈高楼拔地起’,只有地基打得牢,后面才能进行得更加顺利。说了这么多,现在终于步如正题了,了解了为什么要使用Vuex,现在我们就一起探讨一下如何使用它:

4.1 构建vue-cli项目

为了方便我们后续的讲解和开发,这里我们使用vue的脚手架工具vue-cli来构建我们的项目,简单列一下操作步骤:

  1. 开发工具:VSCode
  2. 开发环境:Node.js环境(npm包管理工具)
  3. 安装vue-cli
  4. 创建vue-cli项目

(1)安装vue-cli

执行命令:

npm install -g vue-cli
复制代码

安装成功:

检查是否安装成功,执行命令:

vue -V
复制代码

出现版本号则安装成功:

(2)构建vue-cli项目

执行命令:

vue create [项目名称] 或者 vue init webpack(老版本)
复制代码

如图按需勾选是否安装(router建议安装):

安装完成后我们的项目结构大致如下样子,使用vue-cli3.X版本构建的可能会有所不同:

最后检查项目是否能够正常启动:

执行命令:

npm run dev
复制代码

出现如图则构建成功:

4.2 安装Vuex并引入项目

(1)安装vuex

执行命令:

npm install vuex --save
复制代码

如图:

(2)引入vuex

1.src目录下新建一个store文件夹,并在store目录下新建index.js

2.index.js里面使用vuex,添加如下代码:

import Vue from 'vue'
import Vuex from 'vuex'

//使用vuex
Vue.use(Vuex)

//导出store
export default new Vuex.Store({})
复制代码

3. 在main.js中引入store,然后全局注入一下,这样就可以在任何一个组件里面使用它了:

// The Vue build version to load with the `import` command
// (runtime-only or standalone) has been set in webpack.base.conf with an alias.
import Vue from 'vue'
import App from './App'
import router from './router'
//引入store
import store from './store'

Vue.config.productionTip = false

/* eslint-disable no-new */
new Vue({
  el: '#app',
  router,
  //注册store
  store,
  components: { App },
  template: '<App/>'
})
复制代码

5.开始使用Vuex

5.1 简单看图理解

在正式开始之前,我们先简单看一张图,这一张图很清晰的描述了使用vuex的整个流程,在这里也不用搞得多明白,心里有个概念就好了,到了后面的代码部分自然就会理解了:

我们可以看到最左边是我们的组件,可以简单理解为我们的视图,虚线框则是我们的vuex,这里面有一个State,可以把它理解为数据。以前我们将数据放在组件里面,要修改数据直接在组件里面修改就好了,现在数据放在了vuex仓库里面,我们要修改数据就得走一定的流程:

  • 我们需要在我们组件里面调用Dispatch()方法提交Actions(还记得最开始我们如何说的Actions吗?)\

  • Actions再通过Commit()方法提交Mutations(简单理解为真正的修改数据的方法)\

  • 通过Mutations里面的方法改变state(数据)\

  • 响应(渲染)到组件里面。\

5.2 简单理解StateActionsMutations

看了上面的图可能有一些小伙伴不太理解图里面的StateActionsMutations,这里我们就详细理解一下:

  • state是什么?

官方的话可能有些晦涩,我们可以简单的理解为:存数据的地方,所有的数据都要存在state里面。这样理解起来就简单多了

  • Actions是什么?

Actions和Mutations比较类似,包含的都是一些方法,不同的是Actions不能直接更改数据,它的作用是提交Mutations,Mutations里面包含的才是具体操作数据的方法。

  • Mutations是什么?

vuex中,唯一能够修改数据的方法就是提交mutation,简单来说mutations里面存的就是一些操作数据的方法。

5.3 代码实例

1. 将项目自动生成的HelloWorld.vue删除,新建三个我们自己的组件,分别是:ChildA.vueChildB.vueParent.vue,并配置路由:

项目结构变为:

ChildA.vue

<template>
  <div class="child-a">
    <p>ChildA:{{count}}</p>
    <button>ChildA-Add</button>
  </div>
</template>

<script>
export default {
  name: 'ChildA',
  data () {
    return {
      //我们不再将数据放到组件里
      //count: 0
    }
  }
}
</script>
<style scoped>
</style>
复制代码

ChildB.vue

<template>
  <div class="child-b">
      <p>ChildB:{{count}}</p>
      <button>ChildB-Add</button>
  </div>
</template>

<script>
export default {
  name: 'ChildB',
  data () {
    return {
      //count: 0
    }
  }
}
</script>
<style scoped>

</style>
复制代码

Parent.vue

<template>
  <div class="parent">
      <child-a></child-a>
      <child-b></child-b>
  </div>
</template>

<script>
import ChildA from './ChildA'
import ChildB from './ChildB'
export default {
  name: 'Parent',
  data () {
    return {

    }
  },
  components: {
      ChildA,
      ChildB
  }
}
</script>

<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped>
</style>
复制代码

修改router下的index.js

import Vue from 'vue'
import Router from 'vue-router'
import Parent from '@/components/Parent'

Vue.use(Router)

export default new Router({
  routes: [
    {
      path: '/',
      name: 'Parent',
      component: Parent
    }
  ]
})
复制代码

这个时候我们启动项目,浏览器访问:

我们可以看到,浏览器上面并没有显示{{count}}的内容,因为我们并没有在组件里面定义count,考虑到ChildAChildB公用一个数据count,所以我么们将count提出来,利用vuex的仓库进行管理,所以我们需要在store目录下里面的index.js里面定义count,那么怎么定义呢,我们一起来看看:

修改src/store/index.js文件代码:

import Vue from 'vue'
import Vuex from 'vuex'

//使用vuex
Vue.use(Vuex)

//导出store
export default new Vuex.Store({
    //将数据定义在state里面,state是一个对象
    state: {
        count: 0
    }
})
复制代码

我们已经定义好了count,那么要怎么在各个组件里面使用这个数据呢?我们可以在组件里面这样调用:

CHildA.vue

export default {
  name: 'ChildA',
  data () {
    return {
      //count: 0
    }
  },
  //通过计算属性来获得count
  computed: {
    count: function(){
      //通过vue的this.$store来获得state
      return this.$store.state.count
    }
  }
}
</script>
复制代码

ChildB.vue

<script>
export default {
  name: 'ChildB',
  data () {
    return {
      //count: 0
    }
  },
  computed: {
      count(){
          return this.$store.state.count
      }
  }
}
</script>
复制代码

从上面的代码我们可以很清楚的看到是如何获取到state里面的count的,很多小伙伴可能就有疑惑了,为什么要使用计算属性呢,而不把count定义在data里面,然后通过方法获取呢?在这里我们就要回顾一下vuex里面state的特性了,state状态是响应式的,也就是说如果state里面的count发生了变化,组件里面的数据是自动更新的,所以就需要用到computed属性了。对computed属性不熟的小伙伴可以参考:

关于this.$store:我们最开始把vuex挂载到了根组件上去,所以我们在任何地方都可以调用$store,和$router一个道理,就像是调用store这个大仓库里面的属性一样。

此时我们看到浏览器页面上已经出现了count的值:

2.给两个button添加点击事件,以此来更改count的值:

ChildA.vue(ChildB修改代码相同,所以不在这里重复展示)

<template>
  <div class="child-a">
    <p>ChildA:{{count}}</p>
    <button @click="handleClick(10)">ChildA-Add</button>
  </div>
</template>

<script>
export default {
  name: 'ChildA',
  data () {
    return {
      //count: 0
    }
  },
  computed: {
    count: function(){
      return this.$store.state.count
    }
  },
  methods: {
    handleClick:function(num){
    //通过dispatch触发actions中的方法countAdd,actions提交mutations,num是携带的参数
      this.$store.dispatch('countAdd',num)
    }
  }
}
</script>
<style scoped>

</style>
复制代码

修改src/store/index.js代码:

import Vue from 'vue'
import Vuex from 'vuex'

//使用vuex
Vue.use(Vuex)

//导出store
export default new Vuex.Store({
    state: {
        count: 0
    },
    //组件通过dispatch方法触发actions里面的countAdd方法,然后actions提交mutations里面的countAdd方法。
    actions: {
    //接收组件传过来的参数num,Action 函数接受一个与 store 实例具有相同方法和属性的 context 对象
        countAdd(context,num){
            context.commit('countAdd',num)
        }
    },
    mutations: {
    //传入一个state对象,接收传过来的参数num
        countAdd(state,num){
            state.count+=num
        }
    }
})
复制代码

上面的代码还是比较清晰明了的,较为重要的部分我也添加了注释。在这里我们给button按钮添加了一个点击事件handleClick(),这个方法的作用就是增加count的值,增加多少以传入的参数决定,在方法里面我们通过disptch()方法触发actions,然后actions在提交mutations,最终实现state数据的更改。在actions里面我们接收了一个context对象,可以简单将这个对象理解为store对象(其实不同)。

最终效果:

我们可以看到,点击任意组件的按钮,页面上的其他组件的count值也会跟着改变,因为这些组件通过vuex实现了count的共享,并且count的值是自动更新的,有没有觉得很奇妙!

3.直接出发actions

在刚才的组件中,我们通过dispatch方法出发actions的,那能不能直接触发actions呢?答案是可以的,此时我们的handleClick()方法可以改写为:

methods: {
    handleClick:function(num){
    //直接调用commit触发actions
      this.$store.commit('countAdd',num)
    }
}
复制代码

既然我们直接触发了actions,那么src/store/index.js里面的actions就可以去掉了,因为我们直接commit了。

那么src/store/index.js里面的代码就可以简化为:

import Vue from 'vue'
import Vuex from 'vuex'

//使用vuex
Vue.use(Vuex)

//导出store
export default new Vuex.Store({
    state: {
        count: 0
    },
    mutations: {
        countAdd(state,num){
            state.count+=num
        }
    }
})
复制代码

启动项目可以发现效果是一样的。

到这里可能就有小伙伴疑问了,为什么不能直接执行mutations里面的方法?这样多简单,答案是不能的,具体原因就不再这里深究了,有兴趣的小伙伴可以参考:Mutations详解

5.4 使用对象展开运算符简化代码

代码写到这里,可能有小伙伴发现:如果我们很多地方需要用到store里面的数据,那我们就要在很多地方写this.$store。这样看起来代码是十分冗余的。所以为了解决这个问题,我们引入了对象展开运算符mapState 、mapMutations来简化。

运用mapState 、mapMutations,我们组件里面的代码就可以这样写:

ChileA.vue(Child.vue里面修改的代码与之类似)

<template>
  <div class="child-a">
  //使用新的countA
    <p>ChildA:{{countA}}</p>
    <button @click="handleClick(10)">ChildA-Add</button>
  </div>
</template>

<script>
//要想使用展开运算符,就要先引入
import { mapState, mapMutations } from 'vuex'
export default {
  name: 'ChildA',
  data () {
    return {
    }
  },
  computed: {
  //通过mapState获得state里面的count,并赋值给countA
    ...mapState({
      countA: 'count'
    })
  },
  methods: {
    handleClick:function(num){
      //this.$store.commit('countAdd',num)
      this.countAdd(num)
    },
    //通过展开运算符提交mutations里面的方法countAdd
    ...mapMutations(['countAdd'])
  }
}
</script>
<style scoped>

</style>
复制代码

看到这里有没有觉得代码简洁了不少呢,没有烦人的this.$store,当state里面的数据更多的时候,更能体现出此种方法的优势。

展开运算符是ES6的语法,不太了解的小伙伴可以参考:

在这里我们只用到了mapStatemapMutations,其实还有其他对象展开符,比如mapActions等等,在这里就不深究了。

5.5 Getter与Module

文章写到这儿,vuex基础知识应该差不多了,虽然我们的代码很简单,但是我们需要理解其中的原理,理解其中每一个知识点,比如对象展开符等等,只有理解透彻了,vuex才算是上手了。

下面我们简单讲解一下vuex里面的Getter和Module模块思想,这里我们粗略的过一遍,深入探讨我们放在下一篇文章。

  • Getter

これは、ストアの計算されたプロパティとして簡単に理解できます。これは、計算されたものと似ています。mapGetters ヘルパー関数 は、 store in getter をローカルの計算済みプロパティにマップするだけです。つまり、mapStateと同様の方法でコンポーネントで使用できます。

以下を参照できます。

具体的な内容は次の記事に載せます

  • モジュール

モジュールは実際には非常に単純です。理解するには、公式Webサイトの次のコードを確認する必要があります。

const moduleA = {
  state: { ... },
  mutations: { ... },
  actions: { ... },
  getters: { ... }
}

const moduleB = {
  state: { ... },
  mutations: { ... },
  actions: { ... }
}

const store = new Vuex.Store({
  modules: {
    a: moduleA,
    b: moduleB
  }
})

store.state.a // -> moduleA 的状态
store.state.b // -> moduleB 的状态
复制代码

このコードは非常に明確で、サブモジュールのアイデアです。以前は単一のストアを使用していましたが、プロジェクトが非常に大きくなると、ストアは非常に複雑に見えるため、サブモジュールのアイデアを使用します。詳細については、公式Webサイトを確認してください。

6.まとめ

記事全体が書かれた後、私はvuexについてより深く理解しました。私はいくつかのポイントを要約しました(要約ではありません)。これは知識の目覚めの呼びかけと見なすことができます。

  • vuex:状態共有
  • なぜvuexを使うのか
  • ストア:共有倉庫
  • 状態:データストレージ
  • ディスパッチ、アクション、ミューテーション
  • データ変更プロセス
  • オブジェクトエキスパンダー
  • getter:ストアの計算されたプロパティ
  • モジュール:モジュラーアイデア

上記の点を頭の中で知っている場合、vuexはエントリーレベルである必要があり、その根底に到達するには自分自身に依存する必要があります。この記事は紹介であり、実際のアプリケーションは自分自身に依存する必要があります。 。記事には多くの欠陥があるはずです、そして私はあなたが私の間違いを訂正することを歓迎します。


さよなら!次の記事でお会いしましょう!

最後に、夕食後にみんなが考えられるように写真を載せます。

おすすめ

転載: juejin.im/post/7087100496762109983