vuex 深刻学习

概述

在开发 复杂vue应用 时,vuex 能够帮咱们管理 多个组件中的共享状态vue

使用 new Vuex.store(options), 能够构建一个 store 实例。store 是 vuex应用 的核心,经过 store.state 能够访问应用中的 共享状态, 经过 store.getters 能够访问 共享状态派生出的新的状态,经过 store.commit 方法能够提交 mutation更改共享状态,经过 store.dispatch 方法能够 派发 action异步提交 mutaionvuex

另外, 若是 应用变得复杂致使store变得比较臃肿 的时候, 咱们能够将 store 分割成 module, 每一个 module 可拥有本身的 stategettersmutationsactions 甚至 嵌套子modules数组

问题

  1. vuex是怎么安装的? 安装过程当中作了哪些工做?缓存

  2. 为何修改state中的数据,会触发更新?bash

  3. getter的工做原理?app

  4. 为何不建议经过 store.state.xx 的方式直接修改vuex的状态?异步

  5. 命名空间对state、getter、mutation、action的影响ide

  6. 严格模式是怎么工做的?函数

  7. state、getter、mutation、action在组件中的使用ui

  8. 其余

安装 vuex

使用 vuex 开发 vue应用 的时候,须要先安装 vuex

vuex 的安装过程以下:

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

Vue.use(Vuex)
复制代码

vue 在经过 use 安装 vuex 的时候, 会自动执行 vuex 提供的 install方法。 vuex 提供的 install 方法以下:

// install

function install (_Vue) {
  if (Vue && _Vue === Vue) {
    {
      console.error(
        '[vuex] already installed. Vue.use(Vuex) should be called only once.'
      );
    }
    return
  }
  Vue = _Vue;
  // 执行applyMixin方法
  applyMixin(Vue);
}
复制代码

vuex 提供的 install 方法中,主要过程是执行 applyMixin 方法,具体过程以下:

// applyMixin

function applyMixin (Vue) {
  // 获取当前使用的vue的版本
  const version = Number(Vue.version.split('.')[0]);

  if (version >= 2) {
    // vue2及以上 使用,每个vue实例构建时触发 beforeCreate 钩子函数, 执行 vuexInit 方法
    Vue.mixin({ beforeCreate: vuexInit });
  } else {
    // vue1 使用
    // override init and inject vuex init procedure
    // for 1.x backwards compatibility.
    const _init = Vue.prototype._init;
    Vue.prototype._init = function (options = {}) {
      options.init = options.init
        ? [vuexInit].concat(options.init)
        : vuexInit;
      _init.call(this, options);
    };
  }
  
  // 每个vue实例构建的时候, 都会执行vuexInit方法
  function vuexInit () {
    // this -> 当前vue实例
    const options = this.$options;
    // store injection
    if (options.store) {
      // 根vue实例 的 $store 属性
      this.$store = typeof options.store === 'function'
        ? options.store()
        : options.store;
    } else if (options.parent && options.parent.$store) {
      // 组件vue实例, $store属性, 指向根vue实例的$store属性
      this.$store = options.parent.$store;
    }
  }
复制代码

applyMixin 方法,全局注册 beforeCreate 钩子,即 每个vue实例在构建的时候都会触发 beforeCreate 钩子,执行 vuexInit 方法

执行 vuexInit 方法的时候, 为 vue实例 建立 $store属性。不论是 根vue实例 仍是 组件vue实例$store属性 都指向经过 new Vuex.store 构建的 store实例,即 全部的 vue实例 的 $store 属性值都相同

综上, vuex 安装的时候就作了 一件事:给 vue全局注册 beforeCreate。当建立 vue实例 的时候,触发 beforeCreate,给 vue实例 添加 $store 属性,指向经过 new Vuex.store 构建的 store实例

vuex - 响应式工做原理

在使用 vuex 开发 vue应用 的时候, 咱们会定义 state, 而后在 组件 中经过 计算属性(computed) 使用定义好的 state。 以下:

var option = {
    state: {
        money: 10000
    },
    mutations: {
        setMoney(state, money) {
            state.money = money
        }
    }
}

var bill = {
    data() {
        return {...}
    },
    computed: {
        finalMoney() {
            // money的单位为 厘
            return parseInt(this.$store.state.money / 1000)
        }
    },
    methods: {
        setDisount() {
            this.$store.commit('setMoney', this.money)
        }
    }
    ...
    
}
复制代码

在上面的 示例 中,帐单组件 在计算 费用 的时候,使用了 vuex状态 - money。 当经过 组件方法 setMoney 修改 vuex状态 - money, 帐单组件以前计算的费用 也会 更新

那么问题来了, vuex的响应式更新是怎么触发的?

其实,vuex的响应式原理 是基于 vue的响应式原理 实现的

构建vue实例 的时候,若是 配置项 中包含 数据属性data, 会 深度遍历data中的每个属性,为 每个属性 创建一个 deps列表,并经过 defineProperty 方法为 每个属性 添加 gettersetter。若是 template模板计算属性监听属性 使用了 data属性, 会 触发data属性的getter,对应的 watcher 会添加到 data属性的deps列表 中。 当 data属性 发生变化时, 触发setter通知deps列表中的watcher更新 ,而后 从新渲染界面返回新的计算属性的值触发监听callback

vuex响应式更新, 是在使用 new Vuex.store(options) 构建 store实例 的时候 实现 的,经历的 主要流程 以下:

  1. 创建 根module 以及 子modules

    在构建 store实例 的时候,首先会根据传入的 配置项 options(state、getters、mutation等) 生成一个 root module对象。若是 options 中包含 modules以及嵌套modules, 那么会遍历 modules 及嵌套 modules, 创建对应的 module 对象

    每个 module 对象 都包含各自的 stategettersactonsmutations 以及 嵌套子modules

    module 对象 会经过 children属性(array), 收集 关联的 子module 对象

  2. 嵌套子modules 中的 state 收集到 根modulestate 中。

    // options
    var option = {
        state: {
            name: ''
        },
        modules: {
            trade: {
                state: {
                    money: 1000
                },
                modules: {
                    loan: {
                        state: {
                            discount: 0.8
                        },
                        modules: {
                            confirm: {
                                state: {
                                    num: 2
                                }
                            }
                        }
                    },
                    product: {
                        state: {
                            discount: 0.9
                        }
                    }
                }
            }
        }
    }
    
    
    // 收集 嵌套子module state 之后的 根module state
    state: {
        name: '',
        trade: {
            loan: {
                discount: 0.8,
                confirm: {
                    num: 2
                }
            },
            product: {
                discount: 0.9
            }
        }
    }
    复制代码
  3. 使用 new Vuestore实例 构建一个 vue实例 - _vm

    构建 vue实例 的时候, 根modulestate属性 会做为 data属性 使用。

    store._vm = new Vue({
        data: {
          $$state: state  // state 为 收集子module state 之后的 根module state
        }
     });
    复制代码

    vue实例 构建完成之后, $$state每个属性 都会变成一个 响应式属性,拥有 gettersetter, 维护一个 deps列表

    vue组件 中,咱们能够经过 this.$store.state 的方式访问 _vm实例 中的 $$state属性,原理以下:

    class Store {
        ...
        // 访问 store实例的 state属性,实质为访问 store._vm._data.$$state 属性
        get state () {
            return this._vm._data.$$state
        }
    }
    复制代码

    若是 template模板computed属性watcher属性 中经过 this.$store.state 的方式使用了 某一个vuex状态, 会 触发 vuex状态 在 _vm.$$data 中的 同名属性 的 getter, 对应的 watcher 会添加到 同名属性deps列表 中。 当 修改vuex状态 时, 触发vuex状态在 _vm.$$data 中的 同名属性 的 setter,通知 deps列表 中的 watcher更新,而后 从新渲染界面返回新的计算属性的值触发监听callback

getter工做原理

getter,能够认为是 store计算属性。就像 vue的计算属性 同样, getter的返回值会被缓存,只有 依赖的state状态 发生变化, 才会 从新计算

getter 是基于 vue的计算属性(computed) 实现的。

vue计算属性实现原理 以下:

  1. 构建 vue实例 的时候,须要一个 options配置项,包含 datapropscomputedmethods 等属性。 若是 options 中有 computed属性,须要 遍历computed中的属性,为 每个属性 创建一个 watcher。此外,根据 每个computed属性, 经过 defineProperty 的方式为 vue实例 创建一个 同名属性,设置 getter。 当经过 vue实例 访问 计算属性 的时候,触发getter执行计算属性对应的方法

  2. 每个 计算属性,都对应一个 watcherwatcher 有一个标志属性: dirty。 当 dirty 属性的值为 true 时,获取 计算属性 的值, 须要 执行计算属性对应的方法并缓存返回的结果; 当 dirty 的值为 false 时,返回 缓存的值

    watcher初次构建 的时候,dirty 值为默认为 true

    执行计算属性对应的方法 之后, dirty 的值会置为 false

  3. 第一次 使用 计算属性 的时候, 因为 watcher.dirty 的值为 true,须要 使用计算属性对应的方法。执行方法的时候, 会读取 响应式属性(data、props) 的值, 触发 响应式属性getter方法计算属性watcher 会被添加到 响应式属性deps列表 中。 此外, watcher.dirty 的值会置为 false

  4. 若是依赖的响应式属性发生变化,触发 setter方法, 通知 deps列表 中的 watcher 更新。 此时 watcher.dirty 的值会置为 true下一次 使用 计算属性 的时候, 执行计算属性对应的方法从新计算

  5. 若是依赖的响应式属性没有变化watcher.dirty 的值一直为 false下一次 使用 计算属性 的时候,直接返回 缓存的上一次计算结果

vuexgetter, 也是在使用 new Vuex.store(options) 构建 store实例 的时候 实现 的,经历的主要过程以下:

  1. 创建 根module子modules

  2. 子modules 中的 state 收集到 根modulestate 中。

  3. 根module子modules 中的 getters 收集到 store实例_wrappedGetters 对象中。

  4. store实例_wrappedGetters 对象做为 computed配置项根modulestate对象 做为 data配置 项构建 vue实例 - _vm

    store._vm = new Vue({
        data: {
          $$state: state  // state 为 收集子module state 之后的 根module state
        },
        computed: _wrappedGetters
     });
    复制代码
  5. store实例 添加 getters 属性。 遍历 _wrappedGetters 中属性,经过 defineProperty 的方式,为 store.getters 添加属性,并设置 getter方法,使咱们能够经过 store.getters.xx 的方式访问 store._vm同名计算属性

当咱们在 组件 中经过 this.$store.getters.xx 的方式访问 vuex 定义的 getter 时, 等价于访问 this.$store._vm 中的 同名计算属性

第一次 访问 vuex 的 getter 时,同名计算属性 对应的 watcher.dirty 的值为 true须要执行计算属性对应的方法。 执行的时候, 会 读取依赖的state的值触发state的getter方法,而后读取 this.$store._vm._data.$$data同名响应式属性 的值,触发响应式属性的getter方法。此时,同名计算属性watcher 会被添加到 响应式属性的deps列表 中。同名计算属性 对应的方法执行完毕之后,结果会 缓存watcher.dirty 的值置为 false

若是 vuexgetter 依赖的 state 发生变化,this.$store._vm._data.$$data 中对应的 同名响应式属性setter 会被触发,而后 通知deps列表中的watcher更新vuexgetter同名计算属性watcher.dirty 的值置为 true。 下一次访问 vuexgetter 时,根据 依赖的state,返回 新的值

若是 vuexgetter 依赖的 state 一直没有发生变化,对应的 同名计算属性watcher.dirty 的值一直为 false。 下一次访问 vuexgetter 时,返回 同名计算属性缓存的结果

严格模式

使用 new Vuex.store 构建 store实例 的时候, 若是 strict配置项 的值为 true, 则启用 严格模式

启用 严格模式 之后, 若是 state变动不是由 mutation 引发 的,则会抛出 异常

直接修改state经过mutation修改state, 都是 修改state,为何 直接修改state就会抛出异常 呢?

vuex 中, 有一段源码涉及到 严格模式,以下:

function enableStrictMode (store) {
  store._vm.$watch(function () { return this._data.$$state }, () => {
    {
      assert(store._committing, `do not mutate vuex store state outside mutation handlers.`);
    }
  }, { deep: true, sync: true });
}
复制代码

构建 store实例 的时候, 咱们会为 store实例 构建一个 vue实例_vm。 若是是 严格模式,执行 enableStrictMode 方法。

enableStrictMode 方法中, _vm 会监听 $$state。 若是 $$state 发生变化,则执行 callback

不论是 直接修改state 仍是 经过mutation修改state,最后都会致使 $$state 发生变化,而后 触发callback。 不一样的是,使用 mutation 时,store._committing 值为 true不会抛出异常,而 直接修改 时,store._committing 值为 false会抛出异常

默认状况下, 不启用严格模式。 即若是构建 store实例* 的时候,未设置strict配置项不启用严格模式

注意: 生产环境下, 不要启用严格模式

修改 state

vuex 建议咱们经过 提交mutation 的方式 修改state, 缘由以下:

  1. 严格模式下(strict: ture), 若是经过 store.state.xx = xxx 的方式 修改state, 会 抛出异常

  2. 启用dev-tools(devtools: true) 后, 若是经过 store.state.xx = xxx 的方式 修改statestate的变化 没法被 dev-tools 追踪。

开启 dev-tools 之后, 当咱们经过 提交mutation 的方式 修改state 时, state的变化 能够被 dev-tools 追踪到。 在 dev-toolsvuex列表 中,咱们能够清晰明了的看到每次 commit 操做。

构建 store实例 的时候, dev-tools 插件会经过 store.subscribe 方法 订阅 store 的 mutation,即 注册一个 callback。 当执行 store.commit(type, payload) 时, 先触发 type 对应的 mutations, 而后再触发 callback, 通知 dev-tools 追踪 mutation。 触发 callback时,传入 mutation通过 mutation 后的状态做为参数

命名空间 - namespace

默认状况 下,模块内部的 actionmutationgetter注册在全局命名空间 的,这样使得 多个模块 可以对 同一 mutation 或 action 做出响应。

若是 但愿你的模块具备更高的 封装度 和 复用性,你能够经过添加 namespaced: true 的方式使其成为带 命名空间 的模块。当 模块注册 后,它的全部 getteractionmutation 都会 自动根据模块注册的路径调整命名

命名空间 对模块 getteractionmutation 的影响, 咱们经过一个 示例 来讲明:

var option = {
    // root module
    state: { name: 'root' },
    getters: { getName: state => state.name },
    mutations: { setName: (state, name) => { state.name = name }},
    actions: { setName: (context, name) => { context.commit('setName', name) }},
    modules: {
      // module A
      moduleA: {
        namespaced: true,
        state: { name: 'moduleA' },
        getters: { getName: state => state.name },
        mutations: { setName: (state, name) => { state.name = name }},
        actions: { setName: (context, name) => { context.commit('setName', name) }}
        modules: {
          // module C
          moduleC: {
            state: { name: 'moduleC' },
            getters: { getName: state => state.name },
            mutations: { setName: (state, name) => { state.name = name }},
            actions: { setName: (context, name) => { context.commit('setName', name) }}
          },
          // module D
          moduleD: {
            namespaced: true,
            state: { name: 'moduleD' },
            getters: { getName: state => state.name },
            mutations: { setName: (state, name) => { state.name = name }},
            actions: { setName: (context, name) => { context.commit('setName', name) }}
          }
        }
      },
      // module B
      moduleB: {
        state: { name: 'moduleB' },
        getters: { getName: state => state.name },
        mutations: { setName: (state, name) => { state.name = name }},
        actions: { setName: (context, name) => { context.commit('setName', name) }}
      }
    }
  }
复制代码

在上面的示例中,store 被切割成 root modulemoduleAmoduleBmoduleCmoduleD

root module,无论 namespced 的值为 true 或者 false, 它的 命名空间 都是 , 即为 全局命名空间

moduleAnamespaced 的值为 true, 它的 命名空间'moduleA'

moduleBnamespaced 的值 不是true, 它会 继承 父module 即 root module 的 命名空间,即它的 命名空间全局命名空间

moduleCnamespaced 的值 不是true, 它会 继承 父module 即 moduleA 的 命名空间,即它的 命名空间'moduleA'

moduleDnamespaced 的值为 true, 它的 命名空间'moduleA/moduleD'

命名空间gettersmutationsactions 的使用会有 影响

  • getter

    在构建 store实例 的时候,会将各个 modulegetters 收集到 store._wrappedGetters 中。

    store._wrappedGetters 是一个 对象属性名 为:命名空间/getter名, 对应的 属性值是一个 函数 - 通过包装之后的getter

    因为 _wrappedGetters 是一个 对象, 若是 存在属性名相同的getter,会抛出警告

    在上面的示例中, _wrappedGetters 中, 收集到的 getter 以下:

    _wrappedGetters = {
            'getName': function() {...},  // 根 module 中的 getter
            'moduleA/getName': function() {...}, // moduleA 的 getter
            'moduleA/moduleD/getName': function() {...} // moduleD 的 getter 
        }
    复制代码

    对于 moduleB, 命名空间为 全局命名空间,则对应的属性名为 getName, 此时 _wrappedGetters 中已经存在 同名属性没法添加且抛出警告

    moduleC 的状况也同样,命名空间为 moduleA, 对应的属性为 moduleA/getName_wrappedGetters 中已经存在 同名属性没法添加且抛出警告

    组件 中,咱们能够经过 store.getters.xx 访问 getterxx 会对应 _wrappedGetters 中的 属性名, 以下:

    this.$store.getters.getName  // 访问 根module 的 getName
        
        this.$store.getters['moduleA/getName']  // 访问 moduleA 的 getName
        
        this.$store.getters['moduleA/moduleD/getName'] // 访问 moduleD 的 getName
    复制代码

    moduleBmoduleCgetName 没有收集到 _wrappedGetters 中,在 组件没法访问

  • mutation

    在构建 store实例 的时候,会将各个 modulemutations 收集到 store._mutations 中。

    store._mutations 是一个 对象属性名 为:命名空间/mutation名, 对应的 属性值数组数组元素通过包装的mutation

    在上面的示例中, _mutations 中, 收集到的 mutations 以下:

    _mutations: {
            // fn_root 对应 根module 的 setName, fnB 对应 moduleB 的 setName
            'setName': [fn_root, fnB],
            // fnA 对应 moduleA 的 setName, fnC 对应 moduleC 的 setName
            'moduleA/setName': [fnA, fnC],
            // fnD 对应 moduleD 的 setName
            'moduleA/moduleD/setName': [fnD]
        }
    复制代码

    moduleB命名空间全局命名空间,对应的属性名为 setName, 在 _mutations 已存在, 将 mutation 添加到 setName 对应的 数组中。

    moduleC命名空间moduleA,对应的属性名为 moduleA/setName, 在 _mutations 已存在, 将 mutation 添加到 moduleA/setName 对应的 数组中。

    组件 中,咱们能够经过 store.commit(type, payload) 的方式 提交mutationtype 会对应 _mutations 中的 属性名,以下:

    // 触发 根module、moduleB 的 setName
        this.$store.commit('setName', 'xxx') 
        // 触发 moduleA、 moduleC 的 setName
        this.$store.commit('moduleA/setName', 'xxx')
        // 触发 moduleD 的 setName
        this.$store.commit('moduleA/moduleD/setName', xxx)
    复制代码

    在上面的示例中, root modulemoduleB命名空间 相同,提交 'setName' 时,root modulemoduleB 中的 mutation - setName 都会触发; moduleAmoduleC命名空间 相同, 提交'moduleA/setName' 时, moduleAmoduleC 中的 mutation - setName 都会触发。

  • actions

    在构建 store实例 的时候,会将各个 moduleaction 收集到 store._actions 中。

    store._actions 是一个 对象属性名 为:命名空间/action名, 对应的 属性值数组数组元素通过包装的action

    在上面的示例中, _actions 中, 收集到的 actions 以下:

    _actions: {
            // fn_root 对应 根module 的 setName, fnB 对应 moduleB 的 setName
            'setName': [fn_root, fnB],
            // fnA 对应 moduleA 的 setName, fnC 对应 moduleC 的 setName
            'moduleA/setName': [fnA, fnC],
            // fnD 对应 moduleD 的 setName
            'moduleA/moduleD/setName': [fnD]
        }
    复制代码

    moduleB命名空间全局命名空间,对应的属性名为 setName, 在 _actions 已存在, 将 action 添加到 setName 对应的 数组中。

    moduleC命名空间moduleA,对应的属性名为 moduleA/setName, 在 _actions 已存在, 将 action 添加到 moduleA/setName 对应的 数组中。

    组件 中,咱们能够经过 store.dispatch(type, payload) 的方式 派发 actiontype 会对应 _actions 中的 属性名,以下:

    // 触发 根module、moduleB 的 setName
        this.$store.dispatch('setName', 'xxx') 
        // 触发 moduleA、 moduleC 的 setName
        this.$store.dispatch('moduleA/setName', 'xxx')
        // 触发 moduleD 的 setName
        this.$store.dispatch('moduleA/moduleD/setName', xxx)
    复制代码

    在上面的示例中, root modulemoduleB命名空间 相同,派发 'setName' 时,root modulemoduleB 中的 action - setName 都会触发; moduleAmoduleC命名空间 相同, 派发 'moduleA/setName' 时, moduleAmoduleC 中的 action - setName 都会触发。

state的使用

一般,咱们会在 vue组件 中经过 计算属性 来访问所需的 状态 - state

具体的方式,咱们经过一个 示例 来讲明。

var options = {
    state: {
        name: 'zhangsan'
    },
    modules: {
        trade: {
            namespaced: true,
            state: {
                type: 'waiting'
            },
            modules: {
                product: {
                    namespaced: true,
                    state: {
                        id: 1
                    }
                }
            }
        },
        loan: {
            namespaced: true,
            state: {
                money: 100000
            }
        }
    }
}
复制代码
  • 直接访问

    咱们能够经过 vm.$store.state.xx 的方式直接访问所需的 状态 - state

    var bill = {
        data() {
            return {...}
        },
        computed: {
            name() {
                return this.$store.state.name
            },
            tradeType() {
                return this.$store.state.trade.type
            },
            productId() {
                return this.$store.state.trade.product.id  
            },
            loanMoney() {
                return parseInt(this.$store.state.loan.money / 1000)
            }
        }
    }
    复制代码
  • 经过 mapState 辅助函数

    咱们可使用 辅助函数 - mapState 帮助咱们生成 计算属性

    var mapState = Vuex.mapState
    
    var bill = {
        data() {
            return {...}
        },
        computed: {
            ...mapState({   // 全局命名空间
                name: 'name',
                tradeType: state => state.trade.type // 全局 state
            }),
            ...mpaState('trade/product', { // trade/product 命名空间
                productId: 'id'
            }),
            ...mapState('loan', {  // loan 命名空间
                loanMoney: state => parseInt(state.money / 1000) // 局部 state
            })
        }
    }
    复制代码
  • 使用 createNamespacedHelpers 辅助函数

    咱们可使用 辅助函数 - createNamespacedHelpers、mapState 帮助咱们生成 计算属性

    createNamespacedHelpers 能够帮助咱们生成 基于命名空间辅助函数

    // 基于 全局 命名空间 的 mapState
    var mapState = Vuex.mapState
    // 基于 trade 命名空间 的 mapState
    var tradeMapState = Vuex.createNamespacedHelpers('trade').mapState
    // 基于 trade/product 命名空间 的 mapState
    var productMapState = Vuex.createNamespacedHelpers('trade/product').mapState
    // 基于 loan 命名空间的 mapState 
    var loanMapState = Vuex.createNamespacedHelpers('loan').mapState
    
    var bill = {
        data() {
            return {...}
        },
        computed: {
            ...mapState(['name']), // 计算属性名为 name, 返回值为 this.$store.state.name
            ...tradeMapState({
                tradeType: 'type' // 计算属性名为 tradeType, 返回值为 this.$store.state.trade.type
            }),
            ...productMapState({
                productId: 'id'
            }),
            ...mapState({
                loanMoney: state => parseInt(state.money / 1000)
            })
        }
    }
    复制代码

getter的使用

vuexgetter 的使用,和 state 相似,咱们一样以一个示例来讲明。

var options = {
    state: {
        name: 'zhangsan'
    },
    getters: {
        getName(state) {
            return state.name
        }
    },
    modules: {
        trade: {
            namespaced: true,
            state: {
                type: 'waiting'
            },
            getters: {
              getTradeType(state) {
                  return state.type
              }  
            },
            modules: {
                product: {
                    namespaced: true,
                    state: {
                        id: 1
                    },
                    getters: {
                        getProductId(state) {
                            return state.id
                        }
                    }
                }
            }
        },
        loan: {
            namespaced: true,
            state: {
                money: 100000
            },
            getters: {
                getLoanMoney(state) {
                    return state.money
                }
            }
        },
        confirm: {
            state: {
                num: 100
            },
            getters: {
                getConfirmNum(state) {
                    return state.num
                }
            }
        }
    }
}
复制代码
  • 直接使用

    咱们能够经过 vm.$store.getters.xx 的方式直接访问所需的 getter

    var mapGetters = Vuex.mapGetters
    
    var bill = {
        data() {
            return {...}
        },
        computed: {
            getName() {
                return this.$store.getters.getName
            },
            getTradeType() {
                return this.$store.getters['trade/getTradeType']
            },
            getLoanMoney() {
                return this.$store.getters['loan/getLoanMoney']
            },
            getProductId() {
                return this.$store.getters['trade/product/getProductId']
            },
            getConfirmNum() {
                return this.$store.getters.getConfirmNum
            }
        },
        ...
    }
    复制代码
  • 使用 mapGetters 辅助函数

    咱们可使用 辅助函数 - mapGetters 帮助咱们生成 计算属性

    var bill = {
        data() {
            return {...}
        },
        computed: {
            ...mapGetters(['getName', 'getConfirmNum']),
            ...mapGetters({
                getTradeType: 'trade/getTradeType'
            }),
            ...mapGetters('trade', {
                getProductId: 'product/getProductId'
            }),
            ...mapGetters('loan', {
                'getLoanMoney': 'getLoanMoney'
            })
        },
        ...
    }
    复制代码
  • 使用 createNamespacedHelpers 辅助函数

    咱们可使用 辅助函数 - createNamespacedHelpers、mapGetters 帮助咱们生成 计算属性

    createNamespacedHelpers 能够帮助咱们生成 基于命名空间辅助函数

    // 基于 全局 命名空间 的 mapGetters
    var mapGetters = Vuex.mapGetters
    // 基于 trade 命名空间 的 mapGetters
    var tradeMapGetters = Vuex.createNamespacedHelpers('trade').mapGetters
    // 基于 trade/product 命名空间 的 mapGetters
    var productMapGetters = Vuex.createNamespacedHelpers('trade/product').mapGetters
    // 基于 loan 命名空间的 mapGetters 
    var loanMapGetters = Vuex.createNamespacedHelpers('loan').mapGetters
    
    var bill = {
        data() {
            return {...}
        },
        computed: {
            ...mapGetters(['getName', 'getConfirmNum']),
            ...tradeMapGetters({
                getTradeType: 'getTradeType'
            }),
            ...productMapGetters(['getProductId']),
            ...loanMapGetters(['getLoanMoney'])
        }
    }
    复制代码

mutation的使用

mutation 用于 更改 vuex 的 state, 它的用法和 stategetter 相似,咱们一样以一个示例来讲明。

var options = {
    state: {
        name: 'zhangsan'
    },
    mutations: {
        setName(state, name) {
            state.name = name
        }
    },
    modules: {
        trade: {
            namespaced: true,
            state: {
                type: 'waiting'
            },
            mutations: {
                setTradeType(state, type) {
                    state.type = type
                }
            }
            modules: {
                product: {
                    namespaced: true,
                    state: {
                        id: 1
                    },
                    mutations: {
                        setProductId(state, id) {
                            state.id = id
                        }
                    }
                }
            }
        },
        loan: {
            namespaced: true,
            state: {
                money: 100000
            },
            mutations: {
                setLoanMoney(state, money) {
                    state.money = money
                }
            }
        },
        confirm: {
            state: {
                num: 10
            },
            mutations: {
                setConfirmNum(state, num) {
                    state.num = num
                }
            }
        }
    }
}
复制代码
  • 直接使用

    组件中,咱们能够经过 this.$store.commit(type, payload) 的方式提交 mutation,触发 state状态更改

    var bill = {
        ...
        methods: {
            setName() { 
                this.$store.commit('setName', '李四') 
            },
            setTradeType() { 
                this.$store.commit('trade/setTradeType', 'complete') 
            },
            setProductId() {
                this.$store.commit('trade/product/setProductId', '2')
            },
            setLoanMoney() {
                this.$store.commit('loan/setLoanMoney', '2000000')
            },
            setConfirmNum() {
                this.$store.commit('setConfirmNum', '20')
            }
        }
    }
    复制代码
  • 使用 mapMutations 辅助函数

    咱们可使用 辅助函数 - mapMutations 帮助咱们生成 组件方法 来提交 mutation,触发 state状态更改

    var mapMutations = Vuex.mapMutations
    
    var bill = {
        ...
        methods: {
            // 给 bill组件 添加 setName 方法, 映射 this.$store.commit('setName')
            // 给 bill组件 添加 setConfirmNum 方法, 映射 this.$store.commit('setConfirmNum')
            ...mapMutations(['setName', 'setConfirmNum']),
            // 给 bill组件 添加 setTradeType 方法, 映射 this.$store.commit("trade/setTradeType') ...mapMutations({ setTradeType: 'trade/setTradeType' }), // 给 bill组件 添加 setProductId, 映射 this.$store.commit('trade/product/setProductId') ...mapMutations('trade', { setProductId: 'product/setProductId' }), // 给 bill组件 添加 setLoanMoney, 映射 this.$store.commit('loan/setLoanMoney') ...mapMutations('loan', { setLoanMoney: function(commit, money) { commit('setLoanMoney', money) } }), submit() { // this.$store.commit('setName', '李四') this.setName('李四') // this.$store.commit('setConfirmNum', 20) this.setConfirmNum(20) // this.$store.commit('trade/setTradeType', 'complete') this.setTradeType('complete') // this.$store.commit('trade/product/setProductId', 2345) this.setProductId('2345') // this.$store.commit('loan/setLoanMoney', 2000000) this.setLoanMoney(20000000) } } } 复制代码
  • 使用 createNamespacedHelpers 辅助函数

    咱们可使用 辅助函数 - createNamespacedHelpers、mapMutations 帮助咱们生成 生成 组件方法 来提交 mutation,触发 state状态更改。。

    createNamespacedHelpers 能够帮助咱们生成 基于命名空间辅助函数

    // 基于 全局 命名空间 的 mapMutations
    var mapMutations = Vuex.mapMutations
    // 基于 trade 命名空间 的 mapMutations
    var tradeMapMutations = Vuex.createNamespacedHelpers('trade').mapMutations
    // 基于 trade/product 命名空间 的 mapMutations
    var productMapMutations = Vuex.createNamespacedHelpers('trade/product').mapMutations
    // 基于 loan 命名空间的 mapMutations 
    var loanMapMutations = Vuex.createNamespacedHelpers('loan').mapMutations
    
    var bill = {
        ...
        methods: {
            // 给 bill组件 添加 setName 方法, 映射 this.$store.commit('setName')
            // 给 bill组件 添加 setConfirmNum 方法, 映射 this.$store.commit('setConfirmNum')
            ...mapMutations(['setName', 'setConfirmNum']),
            // 给 bill组件 添加 setTradeType 方法, 映射 this.$store.commit('trade/setTradeType')
            ...tradeMapMutations(['setTradeType']),
            // 给 bill组件 添加 setProductId, 映射 this.$store.commit('trade/product/setProductId')
            ...productMapMutations(['setProductId']),
            // 给 bill组件 添加 setLoanMoney, 映射 this.$store.commit('loan/setLoanMoney') 
            ...loanMapMutations({
                setLoanMoney: function(commit, money) {
                    commit('setLoanMoney', money)
                }
            }),
            submit() {
                // this.$store.commit('setName', '李四')
                this.setName('李四')
                // this.$store.commit('setConfirmNum', 20)
                this.setConfirmNum(20)
                // this.$store.commit('trade/setTradeType', 'complete')
                this.setTradeType('complete')
                // this.$store.commit('trade/product/setProductId', 2345)
                this.setProductId('2345')
                // this.$store.commit('loan/setLoanMoney', 2000000) 
                this.setLoanMoney(20000000)
            }
        }
    }
    复制代码

action的使用

action 相似于 mutation,不一样之处在于:action 提交的是 mutation,而 不是直接变动状态action 能够 包含任意异步操做

action组件 中的使用, 和 mutation 相似,咱们经过一个 示例 来讲明:

var options = {
    state: {
        name: 'zhangsan'
    },
    mutations: {
        setName(state, name) {
            state.name = name
        }
    },
    actions: {
        setName(context, name) {
            setTimeout(() => {
                context.commit('setName', name)
            }, 0)
        }
    }
    modules: {
        trade: {
            namespaced: true,
            state: {
                type: 'waiting'
            },
            mutations: {
                setTradeType(state, type) {
                    state.type = type
                }
            }
            actions: {
                setTradeType(context, type) {
                    setTimeout(() => {
                        context.commit('setTradeType', type)
                    }, 1500)
                }
            }
            modules: {
                product: {
                    namespaced: true,
                    state: {
                        id: 1
                    },
                    mutations: {
                        setProductId(state, id) {
                            state.id = id
                        }
                    },
                    actions: {
                        setProductId(context, id) {
                            setTimeout(() => {
                                context.commit('setProductId', id)
                            }, 3000)
                        }
                    }
                }
            }
        },
        loan: {
            namespaced: true,
            state: {
                money: 100000
            },
            mutations: {
                setLoanMoney(state, money) {
                    state.money = money
                }
            },
            actions: {
                setLoanMoney(context, money) {
                    setTimeout(() => {
                        context.commit('setLoanMoney', money)
                    }, 2000)
                }
            }
        },
        confirm: {
            state: {
                num: 10
            },
            mutations: {
                setConfirmNum(state, num) {
                    state.num = num
                }
            },
            actions: {
                setConfirmNum(context, num) {
                    setTimeout(() => {
                        context.commit('setConfirmNum', num)
                    }, 5000)
                }
            }
        }
    }
}
复制代码
  • 直接使用

    在组件中,咱们能够直接经过 this.$store.dispatch(type, payload) 的方式 派发 action,触发 mutaion提交

    var bill = {
        ...
        methods: {
            setName() { 
                this.$store.dispatch('setName', '李四') 
            },
            setTradeType() { 
                this.$store.dispatch('trade/setTradeType', 'complete') 
            },
            setProductId() {
                this.$store.dispatch('trade/product/setProductId', '2')
            },
            setLoanMoney() {
                this.$store.dispatch('loan/setLoanMoney', '2000000')
            },
            setConfirmNum() {
                this.$store.dispatch('setConfirmNum', '20')
            }
        }
    }
    复制代码
  • 使用 mapActions 辅助函数

    咱们可使用 辅助函数 - mapActions 帮助咱们生成 组件方法 来派发 action,触发 mutation提交

    var mapActions = Vuex.mapActions
    
    var bill = {
        ...
        methods: {
            // 给 bill组件 添加 setName 方法, 映射 this.$store.dispatch('setName')
            // 给 bill组件 添加 setConfirmNum 方法, 映射 this.$store.dispatch('setConfirmNum')
            ...mapActions(['setName', 'setConfirmNum']),
            // 给 bill组件 添加 setTradeType 方法, 映射 this.$store.dispatch("trade/setTradeType') ...mapActions({ setTradeType: 'trade/setTradeType' }), // 给 bill组件 添加 setProductId, 映射 this.$store.dispatch('trade/product/setProductId') ...mapActions('trade', { setProductId: 'product/setProductId' }), // 给 bill组件 添加 setLoanMoney, 映射 this.$store.dispatch('loan/setLoanMoney') ...mapActions('loan', { setLoanMoney: function(dispatch, money) { dispatch('setLoanMoney', money) } }), submit() { // this.$store.dispatch('setName', '李四') this.setName('李四') // this.$store.dispatch('setConfirmNum', 20) this.setConfirmNum(20) // this.$store.dispatch('trade/setTradeType', 'complete') this.setTradeType('complete') // this.$store.dispatch('trade/product/setProductId', 2345) this.setProductId('2345') // this.$store.dispatch('loan/setLoanMoney', 2000000) this.setLoanMoney(20000000) } } } 复制代码
  • 使用 createNamespacedHelpers 辅助函数

    咱们可使用 辅助函数 - createNamespacedHelpers、mapActions 帮助咱们生成 生成 组件方法 来派发 action,触发 mutation提交。。

    createNamespacedHelpers 能够帮助咱们生成 基于命名空间辅助函数

    // 基于 全局 命名空间 的 mapActions
    var mapActions = Vuex.mapActions
    // 基于 trade 命名空间 的 mapActions
    var tradeMapActions = Vuex.createNamespacedHelpers('trade').mapActions
    // 基于 trade/product 命名空间 的 mapActions
    var productMapActions = Vuex.createNamespacedHelpers('trade/product').mapActions
    // 基于 loan 命名空间的 mapActions 
    var loanMapActions = Vuex.createNamespacedHelpers('loan').mapActions
    
    var bill = {
        ...
        methods: {
            // 给 bill组件 添加 setName 方法, 映射 this.$store.dispatch('setName')
            // 给 bill组件 添加 setConfirmNum 方法, 映射 this.$store.dispatch('setConfirmNum')
            ...mapActions(['setName', 'setConfirmNum']),
            // 给 bill组件 添加 setTradeType 方法, 映射 this.$store.dispatch('trade/setTradeType')
            ...tradeMapActions(['setTradeType']),
            // 给 bill组件 添加 setProductId, 映射 this.$store.dispatch('trade/product/setProductId')
            ...productMapActions(['setProductId']),
            // 给 bill组件 添加 setLoanMoney, 映射 this.$store.dispatch('loan/setLoanMoney') 
            ...loanMapActions({
                setLoanMoney: function(dispatch, money) {
                    dispatch('setLoanMoney', money)
                }
            }),
            submit() {
                // this.$store.dispatch('setName', '李四')
                this.setName('李四')
                // this.$store.dispatch('setConfirmNum', 20)
                this.setConfirmNum(20)
                // this.$store.dispatch('trade/setTradeType', 'complete')
                this.setTradeType('complete')
                // this.$store.dispatch('trade/product/setProductId', 2345)
                this.setProductId('2345')
                // this.$store.dispatch('loan/setLoanMoney', 2000000) 
                this.setLoanMoney(20000000)
            }
        }
    }
    复制代码

其余

  1. 不能经过 store.state = xx 方式直接修改 state, 不然会 抛出异常

    class Store {
        ...
        // 访问 store实例的 state属性,实质为访问 store._vm._data.$$state 属性
        get state () {
            return this._vm._data.$$state
        }
        set state (v) {
            {
              assert(false, `use store.replaceState() to explicit replace store state.`);
            }
        }
        ...
        replaceState (state) {
            this._withCommit(() => {
              this._vm._data.$$state = state;
            });
        }
    }
    复制代码

    为何不能在 set state 中, 使用 replaceState中的方式修改 state?? 暂未理解。

  2. 经过 store.watch 能够 响应式地侦听 fn 的返回值,当值改变时 调用callback

    fn 接收 storestate 做为第一个参数,其 getter 做为第二个参数, 只要 vuex状态 发生 变化fn 都会执行, 若是 返回的值和上一次不同, 触发 callback

  3. 经过 store.subscribe 方法能够 订阅 store 的 mutation。 当咱们经过 store.commit(type, payload) 的方式提交 mutation 时, 先触发 type 对应的 mutation, 再触发 subscribe 注册的 handler

    vue-devtools 就是利用 store.subscribe 来追踪 应用中提交的 mutation

    store.subscribe((mutation, state) => {
        // vue-devtools 提供的 devtoolHook
        devtoolHook.emit('vuex:mutation', mutation, state);
    });
    复制代码

    store.subscribe 通常被 插件 调用。

  4. 经过 store.subscribeAction 方法能够订阅 storeaction。 用法以下:

    store.subscribeAction({
        before: function(action, state) {
            ...
        },
        after: function(action, state) {
            ...
        }
    })
    复制代码

    当咱们经过 store.dispatch(type, payload) 的方式派发 action 时, 先触发 before 对应的 handler,再触发 type 对应的 mutation, 最后触发 after 对应的 handler

    若是 subscribeAction 的参数是一个 函数, 默认当作 before 对应的 handler, 即:

    store.subscribeAction(function(action, state) {
        ...
    })
    复制代码
  5. 经过 store.registerModule 方法能够 动态注册一个 新模块

    registerModule第一个参数,表明 注册的模块名及所在的父module第二个参数注册module须要的配置项, 即 { namespaced, state, getters, actions, mutations, modules }

    // 在 根module 中注册 moduleA
    store.registerModule('moduleA', {...})
    // 在 根module 中注册 moduleA
    store.registerModule(['moduleA'], {...})
    // 在 moduleB 中注册 moduleC
    store.registerModule(['moduleA', 'moduleB', 'moduleC'],  {...})
    复制代码

    注意,在第三个示例中, 若是 moduleA根module 中不存在, moduleBmoduleA 中不存在, 会抛出 异常

    动态注册一个新模块之后, store实例会从新收集state、getters、mutations、actions, 从新为store实例构建vm实例(响应式原理)

  6. 经过 store.unregisterModule 方法能够卸载一个动态模块。

    unregisterModule 只有 一个参数, 表明须要 卸载的模块及所在的父module

    // 在 根module 中卸载 moduleA
    store.unregisterModule('moduleA')
    // 在 根module 中卸载 moduleA
    store.unregisterModule(['moduleA'])
    // 在 moduleB 中卸载 moduleA
    store.unregisterModule(['moduleA', 'moduleB', 'moduleC'])
    复制代码

    卸载的模块必须是动态注册的模块,不然抛出异常。 缘由是: 卸载静态module 时, 静态module 不会从 父module 中移除, 可是 静态module 对应的 state 会被移除, 若是 vue组件 中有使用 静态module 中的 state,会报错。 这里应该是vux的一个bug吧。

    若是 要卸载的模块在父模块中不存在,会 报错。 这里应该也是一个bug。

    若是 要卸载的模块的父模块的路劲不对, 会 报错

    卸载一个动态注册的模块之后, store实例会从新收集state、getters、mutations、actions, 从新为store实例构建vm实例(响应式原理)

  7. devtools 配置项

    为 store实例 打开或者关闭 devtools

    若是将 devtools 设置为 falsevue-devtools 就没法经过 store.subscribe 方法 订阅 store 的 mutation没法追踪store提交的mutation