前言

我们需要解决多个组件间的数据通信和状态管理就显得难以维护的问题,在vue中用的是vuex,在react中用的是redux.通过本篇教程将基本熟悉它的所有常用用法以及注意事项。

用于解决什么问题?

用于解决组件之间数据共享,数据的集中处理,一种状态管理的设计模式。

什么情况下我应该使用 Vuex?

如果您需要构建一个中大型单页应用,您很可能会考虑如何更好地在组件外部管理状态,反之,一个简单的 store 模式就足够您所需了

什么是vuex ?单项数据流

flow.png
但是,当我们的应用遇到多个组件共享状态时,单向数据流的简洁性很容易被破坏:

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

    Vuex 背后的基本思想(引入)

对于问题一,传参的方法对于多层嵌套的组件将会非常繁琐,并且对于兄弟组件间的状态传递无能为力。对于问题二,我们经常会采用父子组件直接引用或者通过事件来变更和同步状态的多份拷贝。以上的这些模式非常脆弱,通常会导致无法维护的代码。

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

vuex的组成部分(应用结构)

vuex.png

基本使用

  1. #安装
  2. npm install --save vuex
  3. #引入 一般我们会在src根目录下创建一个store的文件夹,
  4. #下面写index.js来实现相关的存储方法(组件共用数据)
  5. import Vuex from 'vuex'
  6. import Vue from 'vue'
  7. Vue.use(Vuex)

State

State负责存储整个应用的状态数据,一般需要在使用的时候在跟节点注入store对象,后期就可以使用this.$store.state直接获取状态。一般是在main.js文件中引入store的文件,从而使用。(vue spa应用中)

  1. //store为实例化生成的
  2. import store from './store'
  3. new Vue({
  4. el: '#app',
  5. store,
  6. render: h => h(App)
  7. })

这个store可以理解为一个容器,包含着应用中的state等。实例化生成store的过程是:

  1. const mutations = {...};
  2. const actions = {...};
  3. const state = {...};
  4. Vuex.Store({
  5. state,
  6. actions,
  7. mutation
  8. });

后续在组件中使用的过程中,如果想要获取对应的状态你就可以直接使用this.$store.state获取,当然,也可以利用vuex提供的mapState辅助函数将state映射到计算属性中去,如

  1. // 我是组件
  2. let name=this.$store.state.name
  3. import {mapState} from 'vuex'
  4. export default {
  5. computed: mapState({
  6. count: state => state.count
  7. })
  8. }

Mutations

Mutations的中文意思是“变化”,利用它可以更改状态,本质就是用来处理数据的函数,其接收唯一参数值state。store.commit(mutationName)是用来触发一个mutation的方法。需要记住的是,定义的mutation必须是同步函数,否则devtool中的数据将可能出现问题,使状态改变变得难以跟踪。

  1. const mutations = {
  2. mutationName(state) {
  3. //在这里改变state中的数据
  4. }
  5. }
  6. 在组件中触发:
  7. //我是一个组件
  8. export default {
  9. methods: {
  10. handleClick() {
  11. this.$store.commit('mutationName')
  12. }
  13. }
  14. }

或者使用辅助函数mapMutations直接将触发函数映射到methods上,这样就能在元素事件绑定上直接使用了。如:

  1. import {mapMutations} from 'vuex'
  2. //我是一个组件
  3. export default {
  4. methods: mapMutations([
  5. 'mutationName'
  6. ])
  7. }

Actions

Actions也可以用于改变状态,不过是通过触发mutation实现的,重要的是可以包含异步操作。其辅助函数是mapActions与mapMutations类似,也是绑定在组件的methods上的。如果选择直接触发的话,使用this.$store.dispatch(actionName)方法。action内部支持异步方法,这是很重要的一点区别。

  1. //定义Actions,以下方法用了es5的解构
  2. const actions = {
  3. actionName({ commit }) {
  4. //dosomething
  5. commit('mutationName')
  6. }
  7. }
  8. //等同于下面的代码
  9. const actions = {
  10. actionName(context ) {
  11. //dosomething
  12. context.commit('mutationName')
  13. }
  14. }

在组件中使用

  1. import {mapActions} from 'vuex'
  2. //我是一个组件
  3. export default {
  4. methods: mapActions([
  5. 'actionName',
  6. ])
  7. }
  8. //分发action
  9. store.dispatch('actionname')

Getters

有些状态需要做二次处理,就可以使用getters。通过this.$store.getters.valueName对派生出来的状态进行访问。或者直接使用辅助函数mapGetters将其映射到本地计算属性中去。这个属性相当于vuex的计算属性。

  1. const getters = {
  2. strLength: state => state.aString.length
  3. }
  4. // Getter 也可以接受其他 getter 作为第二个参数:
  5. getters: {
  6. // ...
  7. doneTodosCount: (state, getters) => {
  8. return getters.doneTodos.length
  9. }
  10. }
  11. //上面的代码根据aString状态派生出了一个strLength状态
  12. 在组件中使用
  13. import {mapGetters} from 'vuex'
  14. //我是一个组件 ,可以利用mapGetters
  15. export default {
  16. computed: mapGetters([
  17. 'strLength'
  18. ])
  19. // 建议写法
  20. /*
  21. computed: {
  22. ...mapGetters(['platform'])
  23. },
  24. */
  25. }
  26. //直接使用getters
  27. computed:{namelen(){
  28. return this.$store.getters.namelen
  29. }
  30. }

Plugins

插件就是一个钩子函数,在初始化store的时候引入即可。比较常用的是内置的logger插件,用于作为调试使用。

  1. import createLogger from 'vuex/dist/logger'
  2. const store = Vuex.Store({
  3. ...
  4. plugins: [createLogger()]
  5. })

模块化

随着项目的复杂性增加,我们共享的状态越来越多,这时候我们就需要把我们状态的各种操作进行一个分组,分组后再进行按组编写。

  1. // 一般我们用常量申明模块组:
  2. const moduleA={
  3. state,mutations,getters,actions
  4. }
  5. export default new Vuex.Store({
  6. modules:{a:moduleA}
  7. })
  8. // 基本使用 ,加上state.a的命名空间即可
  9. computed:{
  10. count(){
  11. return this.$store.state.a.count;
  12. }
  13. },

注意事项

更新数据

正如文档中所说,我们应该将所有的数据修改都定义在mutations中,这才是科学正确的做法。

state更改报错

Error in v-on handler: “Error: [vuex] use store.replaceState()
to explicit replace store state.”
Error: [vuex] use store.replaceState() to
explicit replace store state.

报错如上,是因为修改state的时候,写成了this.state = data ,正确的写法应该是 this.state.xxx =data ;

vuex与持久存储localStorage的关系

场景 :发现部分开发在引入持久存储之后,mutation的时候没有改变state,而是直接修改的localStorage,这样会导致没有页面刷新时,拿到的state都是不对的。因为state不会实时监听localStorage的更改。
解决方案
1 更改state的时候,除了更改localStorage,也要更改state,而且更改state才是必须要做的,而更改localStorage只是为了解决刷新页面后数据丢失的问题。(推荐方式,毕竟使用vuex的全套才是正宗)
2 如果你觉得上面的方案每次都写很麻烦,也可以尝试增加locastorage的监听机制,当发生变化的时候,寻找更改对应的state.

拓展写法

在mapState,mapActions,mapMutations使用的时候,为了不影响正常方法的使用,我们可以使用对象以及数组的拓展方法进行拓展。

  1. methods:{
  2. ...mapMutations([
  3. 'add','reduce'
  4. ]),
  5. ...mapActions(['addAction','reduceAction'])
  6. },
  7. // 获取state属性,你也可以采用这种写法
  8. import {mapState} from 'vuex';
  9. computed:mapState({
  10. count:state=>state.count
  11. })

单页中直接定义使用(特别说明)

  1. // 如果在模块化构建系统中,请确保在开头调用了 Vue.use(Vuex)
  2. const store = new Vuex.Store({
  3. state: {
  4. count: 0
  5. },
  6. mutations: {
  7. increment (state) {
  8. state.count++
  9. }
  10. }
  11. })
  12. //使用的时候 如果想改变数值,必须用事件触发,不是直接改变。
  13. store.commit('increment')
  14. console.log(store.state.count) // -> 1

个人理解

自我实践下来,如果要用好vuex,需要掌握以下的几点。
1 组件数据共享,跨页面数据共享,可以统一管理数据的存储,操作,分发。比如用户数据,比如固定的某些数据是固定某些api获取的且不止用于一个组件或者一个场景。
2 不要形式主义,每个页面都用vuex;每个模块都写getters,actions,常量方法,当你的工程量、数据量达到使用某技术场景的时候,采用某方案会觉得恰如其分
3 处理基于数据的业务逻辑,一般是跨页面跨组件的,比如购买流程对用户余额,购物车,订单的联动影响
4 vuex核心的index做一些模块公用的存储工具,可以配置一些需要的插件或者工具类
5 拓展:数据通讯不止vuex,简单的也可以用event bus,甚至页面内的已经能符合你的需求了
6 actions,支持异步是指更加建议在action中写异步,写复杂的逻辑,而所有的更改state建议写在mutations

其他

最后,还有一些高级用法,如严格模式,测试等可能使用频率不会特别高。有需要的时候查官方文档就可以了。总的来说,Vuex还是相对比较简单的,特别是如果之前有学过Flux,Redux之类的话,上手起来更加容易。

参考文档