概述
在开发 复杂vue应用 时,vuex 可以帮我们管理 多个组件中的共享状态。
使用 new Vuex.store(options), 可以构建一个 store 实例。store 是 vuex应用 的核心,通过 store.state 可以访问应用中的 共享状态, 通过 store.getters 可以访问 共享状态派生出的新的状态,通过 store.commit 方法可以提交 mutation 以 更改共享状态,通过 store.dispatch 方法可以 派发 action 以 异步提交 mutaion。
另外, 如果 应用变得复杂导致store变得比较臃肿 的时候, 我们可以将 store 分割成 module, 每个 module 可拥有自己的 state、getters、mutations、actions 甚至 嵌套子modules。
问题
-
vuex是怎么安装的? 安装过程中做了哪些工作?
-
为什么修改state中的数据,会触发更新?
-
getter的工作原理?
-
为什么不建议通过 store.state.xx 的方式直接修改vuex的状态?
-
命名空间对state、getter、mutation、action的影响
-
严格模式是怎么工作的?
-
state、getter、mutation、action在组件中的使用
-
其他
安装 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 方法为 每一个属性 添加 getter、setter。如果 template模板、计算属性、监听属性 使用了 data属性, 会 触发data属性的getter,对应的 watcher 会添加到 data属性的deps列表 中。 当 data属性 发生变化时, 触发setter,通知deps列表中的watcher更新 ,然后 重新渲染界面、返回新的计算属性的值、触发监听callback。
vuex响应式更新, 是在使用 new Vuex.store(options) 构建 store实例 的时候 实现 的,经历的 主要流程 如下:
-
建立 根module 以及 子modules。
在构建 store实例 的时候,首先会根据传入的 配置项 options(state、getters、mutation等) 生成一个 root module对象。如果 options 中包含 modules以及嵌套modules, 那么会遍历 modules 及嵌套 modules, 建立对应的 module 对象。
每一个 module 对象 都包含各自的 state、getters、actons、mutations 以及 嵌套子modules。
module 对象 会通过 children属性(array), 收集 关联的 子module 对象。
-
将 嵌套子modules 中的 state 收集到 根module 的 state 中。
// 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 } } }
-
使用 new Vue 为 store实例 构建一个 vue实例 - _vm。
构建 vue实例 的时候, 根module 的 state属性 会作为 data属性 使用。
store._vm = new Vue({ data: { $$state: state // state 为 收集子module state 以后的 根module state } });
vue实例 构建完成以后, ?state 中 每一个属性 都会变成一个 响应式属性,拥有 getter、setter, 维护一个 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 的 计算属性 的 实现原理 如下:
-
构建 vue实例 的时候,需要一个 options配置项,包含 data、props、computed、methods 等属性。 如果 options 中有 computed属性,需要 遍历computed中的属性,为 每一个属性 建立一个 watcher。此外,根据 每一个computed属性, 通过 defineProperty 的方式为 vue实例 建立一个 同名属性,设置 getter。 当通过 vue实例 访问 计算属性 的时候,触发getter,执行计算属性对应的方法。
-
每一个 计算属性,都对应一个 watcher。 watcher 有一个标志属性: dirty。 当 dirty 属性的值为 true 时,获取 计算属性 的值, 需要 执行计算属性对应的方法并缓存返回的结果; 当 dirty 的值为 false 时,返回 缓存的值。
watcher 在 初次构建 的时候,dirty 值为默认为 true。
执行计算属性对应的方法 以后, dirty 的值会置为 false。
-
第一次 使用 计算属性 的时候, 由于 watcher.dirty 的值为 true,需要 使用计算属性对应的方法。执行方法的时候, 会读取 响应式属性(data、props) 的值, 触发 响应式属性 的 getter方法。 计算属性 的 watcher 会被添加到 响应式属性 的 deps列表 中。 此外, watcher.dirty 的值会置为 false。
-
如果依赖的响应式属性发生变化,触发 setter方法, 通知 deps列表 中的 watcher 更新。 此时 watcher.dirty 的值会置为 true。 下一次 使用 计算属性 的时候, 执行计算属性对应的方法重新计算。
-
如果依赖的响应式属性没有变化, watcher.dirty 的值一直为 false。下一次 使用 计算属性 的时候,直接返回 缓存的上一次计算结果。
vuex 的 getter, 也是在使用 new Vuex.store(options) 构建 store实例 的时候 实现 的,经历的主要过程如下:
-
建立 根module及 子modules。
-
将 子modules 中的 state 收集到 根module 的 state 中。
-
将 根module 及 子modules 中的 getters 收集到 store实例 的 _wrappedGetters 对象中。
-
以 store实例 的 _wrappedGetters 对象作为 computed配置项,根module 的 state对象 作为 data配置 项构建 vue实例 - _vm。
store._vm = new Vue({ data: { $$state: state // state 为 收集子module state 以后的 根module state }, computed: _wrappedGetters });
-
给 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。
如果 vuex 的 getter 依赖的 state 发生变化,this.$store._vm._data.?data 中对应的 同名响应式属性 的 setter 会被触发,然后 通知deps列表中的watcher更新。vuex 的 getter 的 同名计算属性 的 watcher.dirty 的值置为 true。 下一次访问 vuex 的 getter 时,根据 依赖的state,返回 新的值。
如果 vuex 的 getter 依赖的 state 一直没有发生变化,对应的 同名计算属性 的 watcher.dirty 的值一直为 false。 下一次访问 vuex 的 getter 时,返回 同名计算属性缓存的结果。
严格模式
使用 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, 原因如下:
-
严格模式下(strict: ture), 如果通过 store.state.xx = xxx 的方式 修改state, 会 抛出异常;
-
启用dev-tools(devtools: true) 后, 如果通过 store.state.xx = xxx 的方式 修改state, state的变化 无法被 dev-tools 追踪。
开启 dev-tools 以后, 当我们通过 提交mutation 的方式 修改state 时, state的变化 可以被 dev-tools 追踪到。 在 dev-tools 的 vuex列表 中,我们可以清晰明了的看到每次 commit 操作。
构建 store实例 的时候, dev-tools 插件会通过 store.subscribe 方法 订阅 store 的 mutation,即 注册一个 callback。 当执行 store.commit(type, payload) 时, 先触发 type 对应的 mutations, 然后再触发 callback, 通知 dev-tools 追踪 mutation。 触发 callback时,传入 mutation 和 经过 mutation 后的状态作为参数。
命名空间 - namespace
默认情况 下,模块内部的 action、mutation 和 getter 是 注册在全局命名空间 的,这样使得 多个模块 能够对 同一 mutation 或 action 作出响应。
如果 希望你的模块具有更高的 封装度 和 复用性,你可以通过添加 namespaced: true 的方式使其成为带 命名空间 的模块。当 模块 被 注册 后,它的所有 getter、action 及 mutation 都会 自动根据模块注册的路径调整命名。
命名空间 对模块 getter、action、mutation 的影响, 我们通过一个 示例 来说明:
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 module、 moduleA、 moduleB、 moduleC、 moduleD。
root module,不管 namespced 的值为 true 或者 false, 它的 命名空间 都是 空, 即为 全局命名空间。
moduleA,namespaced 的值为 true, 它的 命名空间为 'moduleA'。
moduleB,namespaced 的值 不是true, 它会 继承 父module 即 root module 的 命名空间,即它的 命名空间 为 全局命名空间。
moduleC,namespaced 的值 不是true, 它会 继承 父module 即 moduleA 的 命名空间,即它的 命名空间 为 'moduleA'。
moduleD,namespaced 的值为 true, 它的 命名空间为 'moduleA/moduleD'。
命名空间 对 getters、 mutations、actions 的使用会有 影响:
-
getter
在构建 store实例 的时候,会将各个 module 的 getters 收集到 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 访问 getter,xx 会对应 _wrappedGetters 中的 属性名, 如下:
this.$store.getters.getName // 访问 根module 的 getName this.$store.getters['moduleA/getName'] // 访问 moduleA 的 getName this.$store.getters['moduleA/moduleD/getName'] // 访问 moduleD 的 getName
moduleB、moduleC 的 getName 没有收集到 _wrappedGetters 中,在 组件 中 无法访问。
-
mutation
在构建 store实例 的时候,会将各个 module 的 mutations 收集到 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) 的方式 提交mutation,type 会对应 _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 module 和 moduleB 的 命名空间 相同,提交 'setName' 时,root module 和 moduleB 中的 mutation - setName 都会触发; moduleA 和 moduleC 的 命名空间 相同, 提交'moduleA/setName' 时, moduleA 和 moduleC 中的 mutation - setName 都会触发。
-
actions
在构建 store实例 的时候,会将各个 module 的 action 收集到 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) 的方式 派发 action,type 会对应 _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 module 和 moduleB 的 命名空间 相同,派发 'setName' 时,root module 和 moduleB 中的 action - setName 都会触发; moduleA 和 moduleC 的 命名空间 相同, 派发 'moduleA/setName' 时, moduleA 和 moduleC 中的 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的使用
vuex 的 getter 的使用,和 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, 它的用法和 state、getter 类似,我们同样以一个示例来说明。
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) } } }
其他
-
不能通过 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?? 暂未理解。
-
通过 store.watch 可以 响应式地侦听 fn 的返回值,当值改变时 调用callback。
fn 接收 store 的 state 作为第一个参数,其 getter 作为第二个参数, 只要 vuex 的 状态 发生 变化, fn 都会执行, 如果 返回的值和上一次不一样, 触发 callback。
-
通过 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 一般被 插件 调用。
-
通过 store.subscribeAction 方法可以订阅 store 的 action。 用法如下:
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) { ... })
-
通过 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 中不存在, moduleB 在 moduleA 中不存在, 会抛出 异常。
动态注册一个新模块以后, store实例会重新收集state、getters、mutations、actions, 重新为store实例构建vm实例(响应式原理)。
-
通过 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实例(响应式原理)。
-
devtools 配置项
为 store实例 打开或者关闭 devtools。
如果将 devtools 设置为 false, vue-devtools 就无法通过 store.subscribe 方法 订阅 store 的 mutation,无法追踪store提交的mutation。