Vuex.Store
- 用法
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
const store = new Vuex.Store({ ...options })
new Vue({
el: '#app',
store: store,
})
- 核心源码
// Vue.use入口
function install(_Vue) {
if (Vue && _Vue === Vue) {
{
console.error(
'[vuex] already installed. Vue.use(Vuex) should be called only once.'
);
}
return
}
Vue = _Vue;
applyMixin(Vue);
}
/**
* 初始化混入
* @param {Object} Vue Vue
*/
function applyMixin(Vue) {
var version = Number(Vue.version.split('.')[0]);
if (version >= 2) {
// 大于或等于2版本,直接使用Vue.mixin进行混入
// beforeCreate生命周期等于: vuexInit函数
Vue.mixin({ beforeCreate: vuexInit });
} else {
// 重写Vue原型上的_init初始化函数
var _init = Vue.prototype._init;
Vue.prototype._init = function (options) {
if ( options === void 0 ) options = {};
options.init = options.init
? [vuexInit].concat(options.init) // new Vue传入的options有init时,格式化options.init = [vuexInit, options.init]
: vuexInit; // 否则,options.init = vuexInit
_init.call(this, options);
};
}
/**
* Vuex初始化函数
*/
function vuexInit() {
// this.$options = Vue.prototype.$options = new Vue传入的对象
var options = this.$options;
// options.store存在时,this.$store = options.store() || options.store
// options.parent & options.parent.$store存在时,this.$store = options.parent.$store
if (options.store) {
this.$store = typeof options.store === 'function'
? options.store()
: options.store;
} else if (options.parent && options.parent.$store) {
this.$store = options.parent.$store;
}
}
}
Vuex.Store构造器选项
state
Vuex store 实例的根 state 对象。和Vue的data类似。
- 使用方式
const store = new Vuex.Store({
state: {
name: 'fanqiewa'
}
})
const vm = new Vue({
// ...
computed: {
name() {
return this.$store.state.name
}
}
})
- 核心源码
var Store = function Store(options) {
// ...
// 注册根module(注册了state、getters、mutations、actions)
installModule(this, state, [], this._modules.root);
// ...
}
function installModule(store, rootState, path, module, hot) {
// 是否是根模块
var isRoot = !path.length;
// ...
if (!isRoot && !hot) {
var parentState = getNestedState(rootState, paht.slice(0, -1));
var moduleName = path[path.length - 1];
store._withCommit(function () {
{
if (moduleName in parentState) {
console.warn(
("[vuex] state field \"" + moduleName + "\" was overridden by a module with the same name at \"" + (path.join('.')) + "\"")
);
}
}
// 给父级state添加属性,属性名为子模块module名,值为子模块的state
Vue.set(parentState, moduleName, module.state);
})
}
// ...
}
var prototypeAccessors$1 = { state: { configurable: true } };
prototypeAccessors$1.state.get = function () {
// this._vm 为Vue实例
// _data 等于Vue实例的data
return this._vm._data.$$state
};
prototypeAccessors$1.state.set = function (v) {
{
// 不能直接更改state对象
assert(false, "use store.replaceState() to explicit replace store state.");
}
};
// 设置state的get和set
Object.defineProperties( Store.prototype, prototypeAccessors$1 );
mutations
在 store 上注册 mutation,处理函数总是接收 state
作为第一个参数(如果定义在模块中,则为模块的局部状态),payload
作为第二个参数(可选)。
在大多数情况下,载荷应该是一个对象,这样可以包含多个字段并且记录的 mutation 会更易读。
- 类型:
{ [type: string]: Function }
- 使用方式
const store = new Vuex.Store({
state: {
count: 1
},
mutations: {
increment (state, n) {
// 变更状态
state.count += n
}
}
})
// 调用方式
store.commit('increment', 10)
- 核心源码
function installModule(store, rootState, path, module, hot) {
// ...
// 遍历mutations,执行回调
module.forEachMutation(function (mutation, key) {
var namespacedType = namespace + key;
registerMutation(store, namespacedType, mutation, local);
});
// ...
}
// 注册程序员定义的mutations
function registerMutation(store, type, handler, local) {
var entry = store._mutations[type] || (store._mutations[type] = []);
entry.push(function wrappedMutationHandler(payload) {
handler.call(store, local.state, payload);
});
}
// 程序员调用的commit
Store.prototype.commit = function commit(_type, _payload, _options) {
var this$1 = this;
// 统一对象形式
var ref = unifyObjectStyle(_type, _payload, _options);
var type = ref.type;
var payload = ref.payload;
var options = ref.options;
var mutation = { type: type, payload: payload };
// 在new Store时注册的mutations
var entry = this._mutations[type];
if (!entry) {
{
console.error(("[vuex] unknown mutation type: " + type));
}
return
}
// 通过_withCommit方式来触发mutations
this._withCommit(function () {
entry.forEach(function commitIterator(handler) {
handler(payload);
});
});
// 取出所有已注册的回调并执行(注册方式:store.subscribe(fn))
// 在mutations执行之后触发
this._subscribers
.slice() // 如果订阅服务器同步调用unsubscribe,则浅拷贝可防止迭代器失效 unsubscribe
.forEach(function (sub) { return sub(mutation, this$1.state); });
if (
options && options.silent
) {
// 载荷中的silent属性已经删除了
// 取消 Vue 所有的日志与警告
console.warn(
"[vuex] mutation type: " + type + ". Silent option has been removed. " +
'Use the filter functionality in the vue-devtools'
);
}
}
action
在 store 上注册 action。处理函数总是接受 context
作为第一个参数,payload
作为第二个参数(可选)。
context
对象包含以下属性:
{
state, // 等同于 `store.state`,若在模块中则为局部状态
rootState, // 等同于 `store.state`,只存在于模块中
commit, // 等同于 `store.commit`
dispatch, // 等同于 `store.dispatch`
getters, // 等同于 `store.getters`
rootGetters // 等同于 `store.getters`,只存在于模块中
}
- 类型:
{ [type: string]: Function }
- 使用方式
const store = new Vuex.Store({
state: {
count: 0
},
mutations: {
increment (state) {
state.count++
}
},
action: {
increment (context) {
context.commit('increment')
}
}
})
// 调用方式
store.dispatch('increment')
- 组合action
action通常都是异步的。这是action和mutation最大的区别。如何体现的呢?
actions: {
actionA ({ commit }) {
return new Promise((resolve, reject) => {
setTimeout(() => {
commit('someMutation')
resolve()
}, 1000)
})
}
}
// 使用
store.dispatch('actionA').then(() => {
// ...
})
- 核心源码
function installModule(store, rootState, path, module, hot) {
// ...
// 遍历actions,执行回调
module.forEachAction(function (mutation, key) {
var type = action.root ? key : namespace + key;
/**
两种形式
actions: {
increment(context) { },
decrement: {
handler: function() { },
// 扩展 表示当前action注册到根对象上
root: true
}
}
*/
var handler = action.handler || action;
registerAction(store, type, handler, local);
});
// ...
}
// 注册程序员定义的actions
function registerAction(store, type, handler, local) {
var entry = store._actions[type] || (store._actions[type] = []);
entry.push(function wrappedActionHandler (payload) {
// modules的注意事项1在此可以体现出来,第一个参数为context,其中包含了6项,第二个参数为dispatch时传递的payload
var res = handler.call(store, {
dispatch: local.dispatch,
commit: local.commit,
getters: local.getters,
state: local.state,
rootGetters: store.getters,
rootState: store.state
}, payload);
if (!isPromise(res)) {
res = Promise.resolve(res);
}
if (store._devtoolHook) {
return res.catch(function (err) {
store._devtoolHook.emit('vuex:error', err);
throw err
})
} else {
return res
}
});
}
// 程序员调用的dispatch
Store.prototype.dispatch = function dispatch(_type, _payload) {
var this$1 = this;
// 统一对象形式
var ref = unifyObjectStyle(_type, _payload);
var type = ref.type;
var payload = ref.payload;
var action = { type: type, payload: payload };
// 在new Store时注册的actions
var entry = this._actions[type];
if (!entry) {
{
console.error(("[vuex] unknown action type: " + type));
}
return
}
try {
// 取出所有已注册的回调并执行(注册方式:store.subscribeAction(fn,options))
// 在actions执行之前触发
this._actionSubscribers
.slice()
.filter(function (sub) { return sub.before; })
.forEach(function (sub) { return sub.before(action, this$1.state); });
} catch (e) {
{
console.warn("[vuex] error in before action subscribers: ");
console.error(e);
}
}
// 确保所有promise对象都执行完
var result = entry.length > 1
? Promise.all(entry.map(function (handler) { return handler(payload); }))
: entry[0](payload);
// 返回一个promise对象
return new Promise(function (resolve, reject) {
result.then(function (res) {
try {
// 取出所有已注册的回调并执行(注册方式:store.subscribeAction(fn,options))
// 在actions执行之后触发
this$1._actionSubscribers
.filter(function (sub) { return sub.after; })
.forEach(function (sub) { return sub.after(action, this$1.state); });
} catch (e) {
{
console.warn("[vuex] error in after action subscribers: ");
console.error(e);
}
}
resolve(res);
}, function (error) {
try {
this$1._actionSubscribers
.filter(function (sub) { return sub.error; })
.forEach(function (sub) { return sub.error(action, this$1.state, error); });
} catch (e) {
{
console.warn("[vuex] error in error action subscribers: ");
console.error(e);
}
}
reject(error);
});
})
}
getters
在store上注册getter,getter方法接受以下参数
state, // 如果在模块中定义则为模块的局部状态
getters, // 等同于store.getters
当定义在一个模块里时会特别一些:
state, // 如果在模块中定义则为模块的局部状态
getters, // 等同于store.getters
rootState, // 等同于store.state
rootGetters, // 所有getters
注册的getter暴露为store.getters
- 类型:
{ [key: string]: Function }
- 使用方式
const store = new Vuex.Store({
state: {
todos: [
{ id: 1, text: '...', done: true },
{ id: 2, text: '...', done: false }
]
},
getters: {
doneTodos: (state, getters) => {
return state.todos.filter(todo => todo.done)
}
}
})
// 调用方式
store.getters.doneTodos // => [{ id: 1, text: '...', done: true }]
- 核心源码
function installModule(store, rootState, path, module, hot) {
// ...
// 遍历getters,执行回调
module.forEachGetter(function (getter, key) {
var namespacedType = namespace + key;
registerGetter(store, namespacedType, getter, local);
});
// ...
}
// 注册程序员定义的getters
function registerGetter (store, type, rawGetter, local) {
if (store._wrappedGetters[type]) {
{
console.error(("[vuex] duplicate getter key: " + type));
}
return
}
store._wrappedGetters[type] = function wrappedGetter (store) {
// modules的注意事项2在此可以体现出来,第一个参数为当前模块的state,第二个参数为当前模块的getters,第三个参数为根节点的state,第四个参数为根节点的getters
return rawGetter(
local.state, // local state
local.getters, // local getters
store.state, // root state
store.getters // root getters
)
};
}
var Store = function Store (options) {
// ...
// 重置storeVM(这里属于初始化)
resetStoreVM(this, state);
}
/**
* 重置store实例
* @param {Object} store Store实例
* @param {Object} state store.state
* @param {*} hot
*/
function resetStoreVM (store, state, hot) {
var oldVm = store._vm;
// 绑定store公共的getters,使用getters时,从这里取值
store.getters = {};
// 重置本地缓存的getters对象
store._makeLocalGettersCache = Object.create(null);
// 已注册的 getters
var wrappedGetters = store._wrappedGetters;
// 定义一个计算属性对象
var computed = {};
forEachValue(wrappedGetters, function (fn, key) {
// 每一个计算属性都是一个单独的闭包函数
computed[key] = partial(fn, store);
Object.defineProperty(store.getters, key, {
// 调用getters时,实际上调用的是Vue实例的computed属性 ---------- important-api-getter 的使用
get: function () { return store._vm[key]; },
enumerable: true
});
});
var silent = Vue.config.silent;
// 取消警告
Vue.config.silent = true;
store._vm = new Vue({
data: {
$$state: state // 利用$$state来存储state
},
computed: computed
});
// ...
}
modules
由于使用单一状态树,应用的所有状态会集中到一个比较大的对象。当应用变得非常复杂时,store对象就有可能变得相当臃肿。
为了解决以上问题,Vuex允许我们将store分割成模块(module)
。每个模块拥有自己的state、mutation、action、getter、甚至是嵌套子模块——从上至下进行同样方式的分割
- 类型:
Object
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 的状态
模块的局部状态
const moduleA = {
state: () => ({
count: 0
}),
mutations: {
increment (state) {
// 这里的 `state` 对象是模块的局部状态
state.count++
}
},
getters: {
doubleCount (state) {
return state.count * 2
}
}
}
注意1:同样,对于模块内部的action,局部状态通过context.state
暴露出来,根节点状态则为context.rootState
:
const moduleA = {
// ...
actions: {
incrementIfOddOnRootSum ({ state, commit, rootState }) {
if ((state.count + rootState.count) % 2 === 1) {
commit('increment')
}
}
}
}
注意2:对于模块内部的getter,根节点状态会作为第三个参数暴露出来:
const moduleA = {
// ...
getters: {
sumWithRootCount (state, getters, rootState) {
return state.count + rootState.count
}
}
}
- 核心源码
// modules集合的构造器
var ModuleCollection = function ModuleCollection (rawRootModule) {
// 注册根modules
this.register([], rawRootModule, false);
};
/** 4、注册module */
ModuleCollection.prototype.register = function register (path, rawModule, runtime) {
var this$1 = this;
// ...
var newModule = new Module(rawModule, runtime);
if (path.length === 0) {
this.root = newModule; // 根module
} else {
// path.slice(0, -1)获取除了数组最后一位的所有项
// ModuleCollection.prototype.get返回指定命名空间下的module,path为空时,返回this.root
var parent = this.get(path.slice(0, -1));
// 将path数组中的最后一位(也就是自己),绑定到parent的_children对象上
parent.addChild(path[path.length - 1], newModule);
}
// 程序员传递的options有modules时
if (rawModule.modules) {
forEachValue(rawModule.modules, function (rawChildModule, key) {
// 注册子模块,path数组添加modules的属性名
this$1.register(path.concat(key), rawChildModule, runtime);
});
}
};
var Store = function Store (options) {
// 存储modules
this._modules = new ModuleCollection(options);
// ...
};
function installModule(store, rootState, path, module, hot) {
// ...
// 遍历子模块(如果有的话,在new ModuleCollection时,已经往_child中添加了子模块,在这里是可以拿得到的),执行回调
module.forEachChild(function (child, key) {
// 安装子模块
installModule(store, rootState, path.concat(key), child, hot);
});
// ...
}
命名空间
默认情况下,模块内部的 action、mutation 和 getter 是注册在全局命名空间的——这样使得多个模块能够对同一 mutation 或 action 作出响应。
如果希望你的模块具有更高的封装度和复用性,你可以通过添加 namespaced: true
的方式使其成为带命名空间的模块。当模块被注册后,它的所有 getter、action 及 mutation 都会自动根据模块注册的路径调整命名。例如:
const store = new Vuex.Store({
modules: {
account: {
namespaced: true,
// 模块内容(module assets)
state: () => ({ ... }), // 模块内的状态已经是嵌套的了,使用 `namespaced` 属性不会对其产生影响
getters: {
isAdmin () { ... } // -> getters['account/isAdmin']
},
actions: {
login () { ... } // -> dispatch('account/login')
},
mutations: {
login () { ... } // -> commit('account/login')
},
// 嵌套模块
modules: {
// 继承父模块的命名空间
myPage: {
state: () => ({ ... }),
getters: {
profile () { ... } // -> getters['account/profile']
}
},
// 进一步嵌套命名空间
posts: {
namespaced: true,
state: () => ({ ... }),
getters: {
popular () { ... } // -> getters['account/posts/popular']
}
}
}
}
}
})
- 类型:
Boolean
启用了命名空间的 getter 和 action 会收到局部化的 getter
,dispatch
和 commit
。换言之,你在使用模块内容(module assets)时不需要在同一模块内额外添加空间名前缀。更改 namespaced
属性后不需要修改模块内的代码。
注意1:在带命名空间的模块内访问全局内容(Global Assets)
如果你希望使用全局 state 和 getter,rootState
和 rootGetters
会作为第三和第四参数传入 getter,也会通过 context
对象的属性传入 action。
若需要在全局命名空间内分发 action 或提交 mutation,将 { root: true }
作为第三参数传给 dispatch
或 commit
即可。
modules: {
foo: {
namespaced: true,
getters: {
// 在这个模块的 getter 中,`getters` 被局部化了
// 你可以使用 getter 的第四个参数来调用 `rootGetters`
someGetter (state, getters, rootState, rootGetters) {
getters.someOtherGetter // -> 'foo/someOtherGetter'
rootGetters.someOtherGetter // -> 'someOtherGetter'
},
someOtherGetter: state => { ... }
},
actions: {
// 在这个模块中, dispatch 和 commit 也被局部化了
// 他们可以接受 `root` 属性以访问根 dispatch 或 commit
someAction ({ dispatch, commit, getters, rootGetters }) {
getters.someGetter // -> 'foo/someGetter'
rootGetters.someGetter // -> 'someGetter'
dispatch('someOtherAction') // -> 'foo/someOtherAction'
dispatch('someOtherAction', null, { root: true }) // -> 'someOtherAction'
commit('someMutation') // -> 'foo/someMutation'
commit('someMutation', null, { root: true }) // -> 'someMutation'
},
someOtherAction (ctx, payload) { ... }
}
}
}
- 核心源码
/**
* 局部化dispatch, commit, getters and state
* 也就是在模块里定义的actions函数,在函数内部,可以使用local定义的dispatch, commit, getters and state
*/
function makeLocalContext (store, namespace, path) {
var noNamespace = namespace === '';
var local = {
dispatch: noNamespace ? store.dispatch : function (_type, _payload, _options) {
// 有命名空间的模块,在注册actions时,局部可以调用这个dispatch方法
var args = unifyObjectStyle(_type, _payload, _options);
var payload = args.payload;
var options = args.options;
var type = args.type;
/**
不传第三个参数或第三个参数中的root为false时,将会调用根的actions
eg:
modules: {
foo: {
namespaced: true,
actions: {
// 在这个模块中, dispatch 和 commit 也被局部化了
// 他们可以接受 `root` 属性以访问根 dispatch 或 commit
someAction ({ dispatch, commit, getters, rootGetters }) {
getters.someGetter // -> 'foo/someGetter'
rootGetters.someGetter // -> 'someGetter'
dispatch('someOtherAction') // -> 'foo/someOtherAction'
dispatch('someOtherAction', null, { root: true }) // -> 'someOtherAction'
commit('someMutation') // -> 'foo/someMutation'
commit('someMutation', null, { root: true }) // -> 'someMutation'
},
someOtherAction (ctx, payload) { ... }
}
}
}
*/
if (!options || !options.root) {
type = namespace + type;
if ( !store._actions[type]) {
console.error(("[vuex] unknown local action type: " + (args.type) + ", global type: " + type));
return
}
}
return store.dispatch(type, payload)
},
commit: noNamespace ? store.commit : function (_type, _payload, _options) {
var args = unifyObjectStyle(_type, _payload, _options);
var payload = args.payload;
var options = args.options;
var type = args.type;
if (!options || !options.root) {
type = namespace + type;
if ( !store._mutations[type]) {
console.error(("[vuex] unknown local mutation type: " + (args.type) + ", global type: " + type));
return
}
}
store.commit(type, payload, options);
}
};
// getter和state对象必须延迟获取,因为它们可能会改变,在vm实例更新时
Object.defineProperties(local, {
getters: {
get: noNamespace
? function () { return store.getters; }
: function () { return makeLocalGetters(store, namespace); }
},
state: {
// 通过一层层查找
get: function () { return getNestedState(store.state, path); }
}
});
return local
}
注意2:在带命名空间的模块注册全局action
若需要在带命名空间的模块注册全局 action,你可添加 root: true
,并将这个 action 的定义放在函数 handler
中。例如:
{
actions: {
someOtherAction ({dispatch}) {
dispatch('someAction')
}
},
modules: {
foo: {
namespaced: true,
actions: {
someAction: {
root: true,
handler (namespacedContext, payload) { ... } // -> 'someAction'
}
}
}
}
}
- 核心源码
// 遍历actions,执行回调
module.forEachAction(function (action, key) {
var type = action.root ? key : namespace + key;
/**
两种形式
actions: {
increment(context) { },
decrement: {
handler: function() { },
// 扩展 表示当前action注册到根对象上
root: true
}
}
*/
var handler = action.handler || action;
registerAction(store, type, handler, local);
});
注意3:带命名空间的绑定函数
当使用 mapState
, mapGetters
, mapActions
和 mapMutations
这些函数来绑定带命名空间的模块时,写起来可能比较繁琐:
computed: {
...mapState({
a: state => state.some.nested.module.a,
b: state => state.some.nested.module.b
})
},
methods: {
...mapActions([
'some/nested/module/foo', // -> this['some/nested/module/foo']()
'some/nested/module/bar' // -> this['some/nested/module/bar']()
])
}
对于这种情况,你可以将模块的空间名称字符串作为第一个参数传递给上述函数,这样所有绑定都会自动将该模块作为上下文。于是上面的例子可以简化为:
computed: {
...mapState('some/nested/module', {
a: state => state.a,
b: state => state.b
})
},
methods: {
...mapActions('some/nested/module', [
'foo', // -> this.foo()
'bar' // -> this.bar()
])
}
- 核心源码
/**
* mapState辅助函数,以减少Vue代码量
* normalizeNamespace执行会返回一个函数,mapState在执行时需要传递两个参数
* 第一个参数可以是字符串(表示命名空间)|| 对象或数组(表示需要映射出来的states)
* 第二个参数字符串数组或对象。对象属性值可以为字符串或函数,为字符串时直接,直接使用,为函数时,该函数第一个形参为state,第二个形参为getters
* @param {String} [namespace] - 模块的命名空间
* @param { Array<string> | Object<string | function> } states 一个对象或数组
* @return {Object} 返回一个对象,是通过states解析出来的key|value形式
* @example
*
* computed: {
* ...mapState('some/nested/module', {
* a: state => state.a,
* b: state => state.b
* })
* }
*
* 即res = { a: fn, b: fn }
* 因为挂载到了computed上,所以调用时不需要fn加()进行调用
*
*/
var mapState = normalizeNamespace(function (namespace, states) {
var res = {};
// 期待一个对象或数组
if ( !isValidMap(states)) {
console.error('[vuex] mapState: mapper parameter must be either an Array or an Object');
}
// normalizeMap格式化states后,变成数组对象key|value形式
normalizeMap(states).forEach(function (ref) {
var key = ref.key;
var val = ref.val;
res[key] = function mappedState () {
var state = this.$store.state;
var getters = this.$store.getters;
if (namespace) {
// 根据命名空间获取module
var module = getModuleByNamespace(this.$store, 'mapState', namespace);
if (!module) {
return
}
// var local = module.context 本地(局部)的dispatch、commit、getters、state
state = module.context.state;
getters = module.context.getters;
}
return typeof val === 'function'
? val.call(this, state, getters)
: state[val]
};
// mark vuex getter for devtools
res[key].vuex = true;
});
//
return res
});
/**
* mapMutations辅助函数,以减少Vue代码量
* normalizeNamespace执行会返回一个函数,mapMutations在执行时需要传递两个参数
* 第一个参数可以是字符串(表示命名空间)|| 对象或数组(表示需要映射出来的states)
* 第二个参数字符串数组或对象。对象属性值可以为字符串或函数,为字符串时直接,直接使用,为函数时,该函数第一个形参为commit,需要手动触发(传入commit类型)
* @param {String} [namespace] - namespace 为空时,表示commit根模块的
* @param { Array<string> | Object<string | function } mutations # 一个对象或数组
* @return {Object}
* eg:
* methods: {
* ...mapActions('some/nested/module', [
* 'foo', // -> this.foo()
* 'bar' // -> this.bar()
* ]),
*
* ...mapActions('some/nested/module', [
* 'fooA': (commit, a) => { // something }, // -> this.fooA('a')
* 'barB': (commit, a) => { // something } // -> this.barB('a')
* ])
*
* }
*/
var mapMutations = normalizeNamespace(function (namespace, mutations) {
var res = {};
if ( !isValidMap(mutations)) {
console.error('[vuex] mapMutations: mapper parameter must be either an Array or an Object');
}
normalizeMap(mutations).forEach(function (ref) {
var key = ref.key;
var val = ref.val;
res[key] = function mappedMutation () {
var args = [], len = arguments.length;
while ( len-- ) args[ len ] = arguments[ len ];
// Get the commit method from store
var commit = this.$store.commit;
if (namespace) {
var module = getModuleByNamespace(this.$store, 'mapMutations', namespace);
if (!module) {
return
}
commit = module.context.commit;
}
return typeof val === 'function'
? val.apply(this, [commit].concat(args)) // val为函数时,{ key: (commit) => { } } val执行时第一个参数为commit,后续commit需要自己手动操作
: commit.apply(this.$store, [val].concat(args)) // 如果val是字符串,则val即为commit的类型,args的payload载荷
};
});
return res
});
/**
* 它将规范化命名空间,返回一个函数
* @param {Function} fn
* @return {Function}
*/
function normalizeNamespace (fn) {
return function (namespace, map) {
if (typeof namespace !== 'string') { // 如果函数执行时第一个参数给的不是字符串,那么就把第一个参数当成map对象,命名空间则为""
map = namespace;
namespace = '';
} else if (namespace.charAt(namespace.length - 1) !== '/') { // 如果命名空间字符串最后一位没有"/"的话,为其加上
namespace += '/';
}
return fn(namespace, map)
}
}
注意4:而且,你可以通过使用 createNamespacedHelpers
创建基于某个命名空间辅助函数。它返回一个对象,对象里有新的绑定在给定命名空间值上的组件绑定辅助函数:
import { createNamespacedHelpers } from 'vuex'
const { mapState, mapActions } = createNamespacedHelpers('some/nested/module')
export default {
computed: {
// 在 `some/nested/module` 中查找
...mapState({
a: state => state.a,
b: state => state.b
})
},
methods: {
// 在 `some/nested/module` 中查找
...mapActions([
'foo',
'bar'
])
}
}
- 核心源码
/**
* createNamespacedHelpers辅助函数,创建基于某个命名空间辅助函数
* @param {String} namespace
* @return {Object} 返回一个对象,包含mapState、mapGetters、mapMutations、mapActions辅助函数
*/
var createNamespacedHelpers = function (namespace) { return ({
mapState: mapState.bind(null, namespace),
mapGetters: mapGetters.bind(null, namespace),
mapMutations: mapMutations.bind(null, namespace),
mapActions: mapActions.bind(null, namespace)
}); };
注意5:模块动态注册
在 store 创建之后,你可以使用 store.registerModule
方法注册模块:
import Vuex from 'vuex'
const store = new Vuex.Store({ /* 选项 */ })
// 注册模块 `myModule`
store.registerModule('myModule', {
// ...
})
// 注册嵌套模块 `nested/myModule`
store.registerModule(['nested', 'myModule'], {
// ...
})
- 核心源码
/** 4、注册module */
ModuleCollection.prototype.register = function register (path, rawModule, runtime) {
var this$1 = this;
// ...
var newModule = new Module(rawModule, runtime);
if (path.length === 0) {
this.root = newModule; // 根module
} else {
// path.slice(0, -1)获取除了数组最后一位的所有项
// ModuleCollection.prototype.get返回指定命名空间下的module,path为空时,返回this.root
var parent = this.get(path.slice(0, -1));
// 将path数组中的最后一位(也就是自己),绑定到parent的_children对象上
parent.addChild(path[path.length - 1], newModule);
}
// 程序员传递的options有modules时
if (rawModule.modules) {
forEachValue(rawModule.modules, function (rawChildModule, key) {
// 注册子模块,path数组添加modules的属性名
this$1.register(path.concat(key), rawChildModule, runtime);
});
}
};
注意6:保留state
在注册一个新 module 时,你很有可能想保留过去的 state,例如从一个服务端渲染的应用保留 state。你可以通过 preserveState
选项将其归档:store.registerModule('a', module, { preserveState: true })
。
当你设置 preserveState: true
时,该模块会被注册,action、mutation 和 getter 会被添加到 store 中,但是 state 不会。这里假设 store 的 state 已经包含了这个 module 的 state 并且你不希望将其覆写。
- 核心源码
/** 7、程序员调用的registerModule | 注册模块 */
Store.prototype.registerModule = function registerModule (path, rawModule, options) {
if ( options === void 0 ) options = {};
if (typeof path === 'string') { path = [path]; }
{
assert(Array.isArray(path), "module path must be a string or an Array.");
// path为空数组时,发出警告。不能通过此方法注册根模块
assert(path.length > 0, 'cannot register the root module by using registerModule.');
}
// 调用ModuleCollection.Prototype.register进行注册(先注册)
this._modules.register(path, rawModule);
// 安装模块 | preserveState保存状态为true时,不把当前模块的state添加到父级模块的state下(后续也就无法获取)
installModule(this, this.state, path, this._modules.get(path), options.preserveState);
// 重置store实例,从新设置了$$state
resetStoreVM(this, this.state);
};
/**
* 安装modules
* @param {Object} store
* @param {Object} rootState 根state
* @param {Array} path 模块路径 ['feature', 'character']
* @param {Object} module 模块
* @param {Boolean} hot
*/
function installModule(store, rootState, path, module, hot) {
// 是否是根模块
var isRoot = !path.length;
// 获取模块的空间名称 eg: 'feature/character/'
var namespace = store._modules.getNamespace(path);
// ...
// (注册子模块时 && 执行Store.prototype.registerModule注册模块时,如果传入的options.preserveState为undefined时)通过判断
if (!isRoot && !hot) {
/**
父级state,也就是说,根state保存的是一个对象,里面有子对象,保存的是子模块的state
eg:
feature.state = parentState = {
age: '18',
character: {
height: '170cm'
}
}
*/
var parentState = getNestedState(rootState, path.slice(0, -1));
// 子模块名称
var moduleName = path[path.length - 1];
store._withCommit(function () {
{
if (moduleName in parentState) {
console.warn(
("[vuex] state field \"" + moduleName + "\" was overridden by a module with the same name at \"" + (path.join('.')) + "\"")
);
}
}
// 给父级state添加属性,属性名为子模块module名,值为子模块的state
// 通过赋值以后,getNestedState就能从根state向子模块的state中取值 ---------- important-api-state 的使用
Vue.set(parentState, moduleName, module.state);
});
}
// ...
}
模块重用
有时我们可能需要创建一个模块的多个实例,例如:
- 创建多个store,他们公用同一个模块
- 在一个store中多次注册同一个模块
如果我们使用一个纯对象来声明模块的状态,那么这个状态对象会通过引用被共享,导致状态对象被修改时 store 或模块间数据互相污染的问题。
实际上这和 Vue 组件内的 data
是同样的问题。因此解决办法也是相同的——使用一个函数来声明模块状态(仅 2.3.0+ 支持):
const MyReusableModule = {
state: () => ({
foo: 'bar'
}),
// mutation, action 和 getter 等等...
}
- 核心源码
// 存储模块
var Module = function Module (rawModule, runtime) {
// ...
// 确保options上的state为一个对象
this.state = (typeof rawState === 'function' ? rawState() : rawState) || {};
};
ModuleCollection.prototype.register = function register (path, rawModule, runtime) {
var this$1 = this;
// ...
var newModule = new Module(rawModule, runtime);
// ...
};
plugins
一个数组,包含应用在 store 上的插件方法。这些插件直接接收 store 作为唯一参数,可以监听 mutation(用于外部地数据持久化、记录或调试)或者提交 mutation (用于内部数据,例如 websocket 或 某些观察者)
- 类型:
Array<Function>
Vuex 的 store 接受 plugins
选项,这个选项暴露出每次 mutation 的钩子。Vuex 插件就是一个函数,它接收 store 作为唯一参数:
const myPlugin = store => {
// 当 store 初始化后调用
store.subscribe((mutation, state) => {
// 每次 mutation 之后调用
// mutation 的格式为 { type, payload }
})
}
- 使用方式
const store = new Vuex.Store({
// ...
plugins: [myPlugin]
})
有时候插件需要获得状态的“快照”,比较改变的前后状态。想要实现这项功能,你需要对状态对象进行深拷贝:
const myPluginWithSnapshot = store => {
let prevState = _.cloneDeep(store.state)
store.subscribe((mutation, state) => {
let nextState = _.cloneDeep(state)
// 比较 prevState 和 nextState...
// 保存状态,用于下一次 mutation
prevState = nextState
})
}
内置 Logger 插件
Vuex 自带一个日志插件用于一般的调试:
import createLogger from 'vuex/dist/logger'
const store = new Vuex.Store({
plugins: [createLogger()]
})
createLogger
函数有几个配置项:
const logger = createLogger({
collapsed: false, // 自动展开记录的 mutation
filter (mutation, stateBefore, stateAfter) {
// 若 mutation 需要被记录,就让它返回 true 即可
// 顺便,`mutation` 是个 { type, payload } 对象
return mutation.type !== "aBlocklistedMutation"
},
actionFilter (action, state) {
// 和 `filter` 一样,但是是针对 action 的
// `action` 的格式是 `{ type, payload }`
return action.type !== "aBlocklistedAction"
},
transformer (state) {
// 在开始记录之前转换状态
// 例如,只返回指定的子树
return state.subTree
},
mutationTransformer (mutation) {
// mutation 按照 { type, payload } 格式记录
// 我们可以按任意方式格式化
return mutation.type
},
actionTransformer (action) {
// 和 `mutationTransformer` 一样,但是是针对 action 的
return action.type
},
logActions: true, // 记录 action 日志
logMutations: true, // 记录 mutation 日志
logger: console, // 自定义 console 实现,默认为 `console`
})
- 核心源码
var Store = function (options) {
// ...
// 插件数组
var plugins = options.plugins; if ( plugins === void 0 ) plugins = [];
// ...
// 执行plugins,传入store
plugins.forEach(function (plugin) { return plugin(this$1); });
// ...
}
strict
在严格模式下,无论何时发生了状态变更且不是由 mutation 函数引起的,将会抛出错误。
- 类型:
boolean
- 默认值:
false
const store = new Vuex.Store({
// ...
strict: true
})
- 核心源码
/**
* 开启严格模式,监听$$state的改变
*/
function enableStrictMode (store) {
// 监听$$state改变时触发回调 利用$$state来存储state
// Vue.prototype.$watch(expOrFn, cb, options)
// deep表示深度监听,sync表示立即执行watch中的回调方法,不需要等到视图渲染完毕
store._vm.$watch(function () { return this._data.$$state }, function () {
{
// store._committing默认为false,通过mutation函数引起的将会触发_withCommit将_committing置为true
// 也就是说,在开启严格模式时,在外部更改$$state会发出警告
assert(store._committing, "do not mutate vuex store state outside mutation handlers.");
}
}, { deep: true, sync: true });
}
devtools
为某个特定的 Vuex 实例打开或关闭 devtools。对于传入 false
的实例来说 Vuex store 不会订阅到 devtools 插件。可用于一个页面中有多个 store 的情况。
- 类型:
boolean
const store = new Vuex.Store({
// ...
devtools: false
})
- 核心源码
var Store = function (options) {
// ...
// 开启调试工具
var useDevtools = options.devtools !== undefined ? options.devtools : Vue.config.devtools;
if (useDevtools) {
devtoolPlugin(this);
}
// ...
}
replaceState
替换 store 的根状态,仅用状态合并或时光旅行调试。
replaceState(state: Object)
- 核心源码
/** 6、程序员调用的replaceState | 重置state */
Store.prototype.replaceState = function replaceState (state) {
var this$1 = this;
this._withCommit(function () {
this$1._vm._data.$$state = state;
});
};
watch
响应式地侦听 fn
的返回值,当值改变时调用回调函数。fn
接收 store 的 state 作为第一个参数,其 getter 作为第二个参数。最后接收一个可选的对象参数表示 Vue 的 vm.$watch
方法的参数。
要停止侦听,调用此方法返回的函数即可停止侦听。
watch(fn: Function, callback: Function, options?: Object): Function
- 核心源码
/** 5、程序员调用的watch | 监听数据 */
Store.prototype.watch = function watch (getter, cb, options) {
var this$1 = this;
{
// 期待的第一个参数类型为Function
assert(typeof getter === 'function', "store.watch only accepts a function.");
}
// 实际上还是得调用Vue的$watch
// 第一个参数执行时会接收到两个参数,第一个为state,第二个为getters
// 第一个参数必须要使用到响应式数据时,才会触发cb回调,cb接收两个参数,第一个为newVal,第二个为oldVal
// eg: getter = function (state, getters) { return state.name + state.age },每当返回值不同时,都会触发cb
return this._watcherVM.$watch(function () { return getter(this$1.state, this$1.getters); }, cb, options)
};
// Vue的$watch方法
Vue.prototype.$watch = function (expOrFn, cb, options) {
var vm = this;
// 判断是否是对象 如果是对象则递归 深层 监听 直到它不是一个对象的时候才会跳出递归
if (isPlainObject(cb)) {
return createWatcher(vm, expOrFn, cb, options)
}
options = options || {};
options.user = true;
var watcher = new Watcher(vm, expOrFn, cb, options);
if (options.immediate) {
try {
cb.call(vm, watcher.value);
} catch (error) {
handleError(error, vm, ("callback for immediate watcher \"" + (watcher.expression) + "\""));
}
}
// $watch返回一个函数,该函数执行时,即可停止监听!
return function unwatchFn() {
watcher.teardown();
}
}
subscribe
订阅 store 的 mutation。handler
会在每个 mutation 完成后调用(详见:commit),接收 mutation 和经过 mutation 后的状态作为参数:
-
subscribe(handler: Function, options?: Object): Function
-
使用方式
store.subscribe((mutation, state) => {
console.log(mutation.type)
console.log(mutation.payload)
})
- 核心源码
/** 3、程序员调用的subscribe */
/**
* 统一订阅commit,每次调用commit时,都会把store._subscribers数组中的所有函数取出并执行
* 相当于commit的回调
* @param {Function} fn 回调
* @param {Object} options 只支持 { prepend: true // 表示往数组前面增加,触发顺序靠前 }
*/
Store.prototype.subscribe = function subscribe (fn, options) {
return genericSubscribe(fn, this._subscribers, options)
};
/**
* 统一订阅 | 将fn添加进subs数组中,options对象的可选属性有prepend(即:往订阅的数组前面添加)
* @param {Function | Object} fn 回调函数 | 对象
* @param {Array} subs 订阅的数组
* @param {Object} options options对象
* @return 返回一个函数,执行这个回调会把传入的fn从subs数组中移除
*/
function genericSubscribe (fn, subs, options) {
if (subs.indexOf(fn) < 0) {
options && options.prepend
? subs.unshift(fn)
: subs.push(fn);
}
return function () {
var i = subs.indexOf(fn);
if (i > -1) {
subs.splice(i, 1);
}
}
}
默认情况下,新的处理函数会被添加到其链的尾端,因此它会在其它之前已经被添加了的处理函数之后执行。这一行为可以通过向 options
添加 prepend: true
来覆写,即把处理函数添加到其链的最开始。
store.subscribe(handler, { prepend: true })
要停止订阅,调用此方法返回的函数即可停止订阅。
subscribeAction
订阅 store 的 action。handler
会在每个 action 分发的时候调用(详见:action)并接收 action 描述和当前的 store 的 state 这两个参数:
subscribeAction(handler: Function, options?: Object): Function
store.subscribeAction((action, state) => {
console.log(action.type)
console.log(action.payload)
})
- 核心源码
/** 4、程序员调用的subscribeAction */
/**
* 统一订阅dispatch,每次调用dispatch时,都会把store._actionSubscribers数组中的所有函数取出并执行
* 相当于dispatch的回调
* @param {Function | Object} fn 回调
* eg: { before: fn, after: fn, error: fn }
* 其中before会在actions执行前触发,after会在actions执行后返回的promise对象中触发,error为错误时触发
*
* @param {Object} options 只支持 { prepend: true // 表示往数组前面增加,触发顺序靠前 }
*/
Store.prototype.subscribeAction = function subscribeAction (fn, options) {
var subs = typeof fn === 'function' ? { before: fn } : fn;
return genericSubscribe(subs, this._actionSubscribers, options)
};
从 3.1.0 起,subscribeAction
也可以指定订阅处理函数的被调用时机应该在一个 action 分发之前还是之后 (默认行为是之前):
store.subscribeAction({
before: (action, state) => {
console.log(`before action ${action.type}`)
},
after: (action, state) => {
console.log(`after action ${action.type}`)
}
})
自 3.4.0 起,subscribeAction
也可以指定一个 error
处理函数以捕获分发 action 的时候被抛出的错误。该函数会从第三个参数接收到一个 error
对象。
store.subscribeAction({
error: (action, state, error) => {
console.log(`error action ${action.type}`)
console.error(error)
}
})
registerModule
动态一个动态模块。
registerModule(path: string | Array<string>, module: Module, options?: Object)
options
可以包含preserveState: true
以允许保留之前的 state。用于服务端渲染。- 核心源码
/** 7、程序员调用的registerModule | 注册模块 */
Store.prototype.registerModule = function registerModule (path, rawModule, options) {
if ( options === void 0 ) options = {};
if (typeof path === 'string') { path = [path]; }
{
assert(Array.isArray(path), "module path must be a string or an Array.");
// path为空数组时,发出警告。不能通过此方法注册根模块
assert(path.length > 0, 'cannot register the root module by using registerModule.');
}
// 调用ModuleCollection.Prototype.register进行注册(先注册)
this._modules.register(path, rawModule);
// 安装模块 | preserveState保存状态为true时,不把当前模块的state添加到父级模块的state下(后续也就无法获取)
installModule(this, this.state, path, this._modules.get(path), options.preserveState);
// 重置store实例,重新设置了$$state
resetStoreVM(this, this.state);
};
unregisterModule
卸载一个动态模块。
unregisterModule(path: string | Array<string>)
- 核心源码
/** 8、程序员调用的unregisterModule | 卸载模块 */
Store.prototype.unregisterModule = function unregisterModule (path) {
var this$1 = this;
if (typeof path === 'string') { path = [path]; }
{
assert(Array.isArray(path), "module path must be a string or an Array.");
}
// 调用ModuleCollection.Prototype.unregister进行卸载
this._modules.unregister(path);
this._withCommit(function () {
var parentState = getNestedState(this$1.state, path.slice(0, -1));
Vue.delete(parentState, path[path.length - 1]);
});
resetStore(this);
};
hasModule
检查该模块的名字是否已经被注册。
hasModule(path: string | Array<string>): boolean
- 核心源码
/** 8、程序员调用的hasModule | 判断是否具有模块 */
Store.prototype.hasModule = function hasModule (path) {
if (typeof path === 'string') { path = [path]; }
{
assert(Array.isArray(path), "module path must be a string or an Array.");
}
// 调用ModuleCollection.Prototype.isRegistered进行判断
return this._modules.isRegistered(path)
};
hotUpdate
热替换新的 action 和 mutation。
hotUpdate(newOptions: Object)
- 核心源码
/** 9、程序员调用的hotUpdate | 热更新 */
Store.prototype.hotUpdate = function hotUpdate (newOptions) {
this._modules.update(newOptions);
resetStore(this, true);
};
/**
* 重置Store(也叫初始化)
*/
function resetStore (store, hot) {
store._actions = Object.create(null);
store._mutations = Object.create(null);
store._wrappedGetters = Object.create(null);
store._modulesNamespaceMap = Object.create(null);
var state = store.state;
// 重新初始化模块
installModule(store, state, [], store._modules.root, true);
// 重置StoreVM
resetStoreVM(store, state, hot);
}
/**
* 重置store实例
* @param {Object} store Store实例
* @param {Object} state store.state
* @param {*} hot
*/
function resetStoreVM (store, state, hot) {
var oldVm = store._vm;
// ...
// 初始化时不执行
if (oldVm) {
if (hot) {
// 在热更新模式下,更改旧Vm的$$state,以达到发布通知,更改视图
store._withCommit(function () {
oldVm._data.$$state = null;
});
}
// 销毁旧VM
Vue.nextTick(function () { return oldVm.$destroy(); });
}
}
组件绑定的辅助函数
辅助函数将会用到的公共方法
/**
* 它将规范化命名空间,返回一个函数
* @param {Function} fn
* @return {Function}
*/
function normalizeNamespace (fn) {
return function (namespace, map) {
if (typeof namespace !== 'string') { // 如果函数执行时第一个参数给的不是字符串,那么就把第一个参数当成map对象,命名空间则为""
map = namespace;
namespace = '';
} else if (namespace.charAt(namespace.length - 1) !== '/') { // 如果命名空间字符串最后一位没有"/"的话,为其加上
namespace += '/';
}
return fn(namespace, map)
}
}
/**
* 格式化map,变成数组对象key|value形式
* normalizeMap([1, 2, 3]) => [ { key: 1, val: 1 }, { key: 2, val: 2 }, { key: 3, val: 3 } ]
* normalizeMap({a: 1, b: 2, c: 3}) => [ { key: 'a', val: 1 }, { key: 'b', val: 2 }, { key: 'c', val: 3 } ]
* @param {Array|Object} map
* @return {Object}
*/
function normalizeMap (map) {
if (!isValidMap(map)) {
return []
}
return Array.isArray(map)
? map.map(function (key) { return ({ key: key, val: key }); })
: Object.keys(map).map(function (key) { return ({ key: key, val: map[key] }); })
}
/**
* 按命名空间从存储区搜索特殊模块。如果模块不存在,则打印错误消息。
* @param {Object} store
* @param {String} helper
* @param {String} namespace
* @return {Object}
*/
function getModuleByNamespace (store, helper, namespace) {
var module = store._modulesNamespaceMap[namespace];
if ( !module) {
console.error(("[vuex] module namespace not found in " + helper + "(): " + namespace));
}
return module
}
mapState
mapState(namespace?: string, map: Array<string> | Object<string | function>): Object
为组件创建计算属性以返回 Vuex store 中的状态。
第一个参数是可选的,可以是一个命名空间字符串。
对象形式的第二个参数的成员可以是一个函数。
- 使用方式
// 在单独构建的版本中辅助函数为 Vuex.mapState
import { mapState } from 'vuex'
export default {
// ...
computed: mapState({
// 箭头函数可使代码更简练
count: state => state.count,
// 传字符串参数 'count' 等同于 `state => state.count`
countAlias: 'count',
// 为了能够使用 `this` 获取局部状态,必须使用常规函数
countPlusLocalState (state) {
return state.count + this.localCount
}
})
}
当映射的计算属性的名称与 state 的子节点名称相同时,我们也可以给 mapState
传一个字符串数组。
computed: mapState([
// 映射 this.count 为 store.state.count
'count'
])
mapState
函数返回的是一个对象。我们如何将它与局部计算属性混合使用呢?通常,我们需要使用一个工具函数将多个对象合并为一个,以使我们可以将最终对象传给 computed
属性。但是自从有了对象展开运算符,我们可以极大地简化写法:
computed: {
localComputed () { /* ... */ },
// 使用对象展开运算符将此对象混入到外部对象中
...mapState({
// ...
})
}
- 核心源码
/**
* mapState辅助函数,以减少Vue代码量
* normalizeNamespace执行会返回一个函数,mapState在执行时需要传递两个参数
* 第一个参数可以是字符串(表示命名空间)|| 对象或数组(表示需要映射出来的states)
* 第二个参数字符串数组或对象。对象属性值可以为字符串或函数,为字符串时直接,直接使用,为函数时,该函数第一个形参为state,第二个形参为getters
* @param {String} [namespace] - 模块的命名空间
* @param { Array<string> | Object<string | function> } states 一个对象或数组
* @return {Object} 返回一个对象,是通过states解析出来的key|value形式
* @example
*
* computed: {
* ...mapState('some/nested/module', {
* a: state => state.a,
* b: state => state.b
* })
* }
*
* 即res = { a: fn, b: fn }
* 因为挂载到了computed上,所以调用时不需要fn加()进行调用
*
*/
var mapState = normalizeNamespace(function (namespace, states) {
var res = {};
// 期待一个对象或数组
if ( !isValidMap(states)) {
console.error('[vuex] mapState: mapper parameter must be either an Array or an Object');
}
// normalizeMap格式化states后,变成数组对象key|value形式
normalizeMap(states).forEach(function (ref) {
var key = ref.key;
var val = ref.val;
res[key] = function mappedState () {
var state = this.$store.state;
var getters = this.$store.getters;
if (namespace) {
// 根据命名空间获取module
var module = getModuleByNamespace(this.$store, 'mapState', namespace);
if (!module) {
return
}
// var local = module.context 本地(局部)的dispatch、commit、getters、state
state = module.context.state;
getters = module.context.getters;
}
return typeof val === 'function'
? val.call(this, state, getters)
: state[val]
};
// mark vuex getter for devtools
res[key].vuex = true;
});
//
return res
});
mapGetters
mapGetters(namespace?: string, map: Array<string> | Object<string>): Object
为组件创建计算属性以返回 getter 的返回值。
第一个参数是可选的,可以是一个命名空间字符串。
- 使用方式
import { mapGetters } from 'vuex'
export default {
// ...
computed: {
// 使用对象展开运算符将 getter 混入 computed 对象中
...mapGetters([
'doneTodosCount',
'anotherGetter',
// ...
])
}
}
如果你想将一个 getter 属性另取一个名字,使用对象形式:
...mapGetters({
// 把 `this.doneCount` 映射为 `this.$store.getters.doneTodosCount`
doneCount: 'doneTodosCount'
})
- 核心源码
/**
* mapGetters辅助函数,以减少Vue代码量
* normalizeNamespace执行会返回一个函数,mapGetters在执行时需要传递两个参数
* 第一个参数可以是字符串(表示命名空间)|| 对象或数组(表示需要映射出来的states)
* 第二个参数字符串数组或对象。对象属性值只能是字符串
* @param { String } [namespace] - 模块的命名空间
* @param { Array<string> | Object<string> } getters # 一个对象或数组
* @return {Object}
*/
var mapGetters = normalizeNamespace(function (namespace, getters) {
var res = {};
if ( !isValidMap(getters)) {
console.error('[vuex] mapGetters: mapper parameter must be either an Array or an Object');
}
normalizeMap(getters).forEach(function (ref) {
var key = ref.key;
var val = ref.val;
// getters的取值需要完整的命名空间
val = namespace + val;
res[key] = function mappedGetter () {
if (namespace && !getModuleByNamespace(this.$store, 'mapGetters', namespace)) {
return
}
if ( !(val in this.$store.getters)) {
console.error(("[vuex] unknown getter: " + val));
return
}
return this.$store.getters[val]
};
// mark vuex getter for devtools
res[key].vuex = true;
});
return res
});
mapActions
mapActions(namespace?: string, map: Array<string> | Object<string | function>): Object
创建组件方法分发 action。
第一个参数是可选的,可以是一个命名空间字符串。
对象形式的第二个参数的成员可以是一个函数。
- 使用方式
在组件中分发 Action
import { mapActions } from 'vuex'
export default {
// ...
methods: {
...mapActions([
'increment', // 将 `this.increment()` 映射为 `this.$store.dispatch('increment')`
// `mapActions` 也支持载荷:
'incrementBy' // 将 `this.incrementBy(amount)` 映射为 `this.$store.dispatch('incrementBy', amount)`
]),
...mapActions({
add: 'increment' // 将 `this.add()` 映射为 `this.$store.dispatch('increment')`
})
}
}
- 核心源码
/**
* mapActions辅助函数,以减少Vue代码量
* normalizeNamespace执行会返回一个函数,mapActions在执行时需要传递两个参数
* 第一个参数可以是字符串(表示命名空间)|| 对象或数组(表示需要映射出来的states)
* 第二个参数字符串数组或对象。对象属性值可以为字符串或函数,为字符串时直接,直接使用,为函数时,该函数第一个形参为dispatch,需要手动触发(传入dispatch类型)
* @param {String} [namespace] - 模块的命名空间
* @param { Array<string> | Object<string | function } actions # 一个对象或数组
* @return {Object}
*/
var mapActions = normalizeNamespace(function (namespace, actions) {
var res = {};
if ( !isValidMap(actions)) {
console.error('[vuex] mapActions: mapper parameter must be either an Array or an Object');
}
normalizeMap(actions).forEach(function (ref) {
var key = ref.key;
var val = ref.val;
res[key] = function mappedAction () {
var args = [], len = arguments.length;
while ( len-- ) args[ len ] = arguments[ len ];
// get dispatch function from store
var dispatch = this.$store.dispatch;
if (namespace) {
var module = getModuleByNamespace(this.$store, 'mapActions', namespace);
if (!module) {
return
}
dispatch = module.context.dispatch;
}
return typeof val === 'function'
? val.apply(this, [dispatch].concat(args))
: dispatch.apply(this.$store, [val].concat(args))
};
});
return res
});
带命名空间的绑定函数
当使用 mapState
, mapGetters
, mapActions
和 mapMutations
这些函数来绑定带命名空间的模块时,写起来可能比较繁琐:
computed: {
...mapState({
a: state => state.some.nested.module.a,
b: state => state.some.nested.module.b
})
},
methods: {
...mapActions([
'some/nested/module/foo', // -> this['some/nested/module/foo']()
'some/nested/module/bar' // -> this['some/nested/module/bar']()
])
}
对于这种情况,你可以将模块的空间名称字符串作为第一个参数传递给上述函数,这样所有绑定都会自动将该模块作为上下文。于是上面的例子可以简化为:
computed: {
...mapState('some/nested/module', {
a: state => state.a,
b: state => state.b
})
},
methods: {
...mapActions('some/nested/module', [
'foo', // -> this.foo()
'bar' // -> this.bar()
])
}
createNamespacedHelpers
createNamespacedHelpers(namespace: string): Object
创建基于命名空间的组件绑定辅助函数。其返回一个包含 mapState
、mapGetters
、mapActions
和 mapMutations
的对象。它们都已经绑定在了给定的命名空间上。
- 使用方式
import { createNamespacedHelpers } from 'vuex'
const { mapState, mapActions } = createNamespacedHelpers('some/nested/module')
export default {
computed: {
// 在 `some/nested/module` 中查找
...mapState({
a: state => state.a,
b: state => state.b
})
},
methods: {
// 在 `some/nested/module` 中查找
...mapActions([
'foo',
'bar'
])
}
}
- 核心源码
/**
* createNamespacedHelpers辅助函数,创建基于某个命名空间辅助函数
* @param {String} namespace
* @return {Object} 返回一个对象,包含mapState、mapGetters、mapMutations、mapActions辅助函数
*/
var createNamespacedHelpers = function (namespace) { return ({
mapState: mapState.bind(null, namespace),
mapGetters: mapGetters.bind(null, namespace),
mapMutations: mapMutations.bind(null, namespace),
mapActions: mapActions.bind(null, namespace)
}); };