Appearance
Store
对于 Vuex 来说 Store 是其核心,其作为一个中央仓库,提供了一下的功能:
- 层级 module 的数据存储功能
- 创建实例 store 对象维护整个功能
Store 初始化过程
js
const store = new Vuex.Store({
modules: {
xxx,
},
})
new Vue({
store,
template: ``,
}).$mount("#app")
Store 对象
Store 是一个 Class 对象,其内置了很多的内部变量去存储和维护整个 module 功能,如
_modules
,_modulesNamespaceMap
维护 module 的_actions
,_actionSubscribers
维护 action 的_wrappedGetters
维护 getter 的_mutations
维护 mutations 的_committing
维护 commit 的状态
也提供了我们开发过程中需要的几个核心属性和方法: state
、commit
、dispatch
。
我们先简单的看一下一个 store 对于我们整个 Vuex 数据的存储结构
js
store = {
_commiting : false,
// 存放整个module树
_modules :{
root : { // Module
a: xxx // Module
}
},
// 按照模块全路径保存所有的局部命名 的 模块
_modulesNamespaceMap : {
‘a/’ : ModuleA,
'a/aa' : ModuleAA
},
_mutations : {
'a/aa/commit1' : function
},
_actions : {
'a/aa/action1' : function
},
_wrappedGetters : {
'a/aa/getter1' : function
},
// 通过一个computed 去 处理所有的计算属性的依赖关系
_vm : { // Vue实例对象 主要关注 data属性和 computed属性
data : {
$$state : store
},
computed:{
... store._wrappedGetters
}
},
// 两个实例方法
commit : function ,
dispatch : function ,
getters : {}
}
从上面的结果我们可以看出对于 Vuex 的树形结构 Module 的功能其转换的结果并不是按照 moduleA.moduleAA...的方式去存储的,而是将子孙 module 的 mutation、getter、action 按照特殊的命名方式统一存放在 store 实例对象下,结构如下 a/aa/commit1
、a/aa/action1
、a/aa/getter1
installModule
核心代码
js
/**
加载Module 在 new ModuleConllection的时候 初始化处理了整个module树,那么这时候处理module树中的 state、mutation 、 action...
重点 : getter action mutation 路径是如何处理的?
我们知道Module中存在 namespaced属性,如果为 true , 使其成为带命名空间的模块. 所有 getter、action 及 mutation 都会自动根据模块注册的路径调整命名。
* @param {*} store // store对象
* @param {*} rootState // 跟state
* @param {*} path // 模块路径 ['a','ab']
* @param {*} module // 模块实例化对象
* @param {*} hot // 是否保留原来的state
*/
function installModule(store, rootState, path, module, hot) {
// 在Store中调用installModule() 中 installModule(this, state, [], this._modules.root)
// path = [];
// 然后深度遍历 module树的时候 path.concat(key) 使得变成 [ 'a' , 'aa']
// 如果path.length === 0 说明这是 根模块
const isRoot = !path.length
// 调用module 的 getNamespace 然后根据子模块 namespaced 去形成 各模块的路径
// [ 'a' , 'aa' , 'aaa'] 中 全有 namespaced:true , a => "a"; 'aa' => 'a/aa'; 'aaa' => 'a/aa/aaa'
// [ 'a' , 'ab' , 'aba'] 中 全有 'ab' 的 namespaced:false , a => "a"; 'ab' => 'a'; 'aba' => 'a/aba'
const namespace = store._modules.getNamespace(path)
// register in namespace map
// 如果当前模块是局部命名空间,那么就需要将当前模块注册到 store._modulesNamespaceMap中
if (module.namespaced) {
store._modulesNamespaceMap[namespace] = module
}
// set state
if (!isRoot && !hot) {
// 获取上一级module的 state对象
const parentState = getNestedState(rootState, path.slice(0, -1))
// 获取当前module的name key
const moduleName = path[path.length - 1]
store._withCommit(() => {
// 通过Vue.set 响应式的将当前state存放到上一级state.moduleName下
Vue.set(parentState, moduleName, module.state)
})
}
const local = (module.context = makeLocalContext(store, namespace, path))
// 在store上添加
module.forEachMutation((mutation, key) => {
const namespacedType = namespace + key
registerMutation(store, namespacedType, mutation, local)
})
// 处理 modules中的 actions 属性
// 如: const actions = { increment: ({ commit }) => commit('increment'),decrement: ({ commit }) => commit('decrement')}
module.forEachAction((action, key) => {
const type = action.root ? key : namespace + key
const handler = action.handler || action
registerAction(store, type, handler, local)
})
module.forEachGetter((getter, key) => {
const namespacedType = namespace + key
registerGetter(store, namespacedType, getter, local)
})
// 通过判断是否存在子 module,然后不断深度遍历从而处理整个 module 树
module.forEachChild((child, key) => {
installModule(store, rootState, path.concat(key), child, hot)
})
}
重点:
state 的存储
代码为
js
// set state
if (!isRoot && !hot) {
// 获取上一级module的 state对象
const parentState = getNestedState(rootState, path.slice(0, -1))
// 获取当前module的name key
const moduleName = path[path.length - 1]
store._withCommit(() => {
// 通过Vue.set 响应式的将当前state存放到上一级state.moduleName下
Vue.set(parentState, moduleName, module.state)
})
}
可以见得对于 state 树的结构为一下方式
modueleA: {
modueleAB : {
modueleABA
}
modueleAC:{
modueleACA
}
}
结果变成
js
state = {
a1: 1,
a2: 2,
// moduleAB
modueleAB: {
ab1: 1,
ab2: 1,
// modueleABA
modueleABA: {
aba1: 1,
aba2: 1,
},
},
// moduleAC
modueleAC: {
ac1: 1,
// modueleACA
modueleACA: {
aca1: 1,
},
},
}
action 和 getter 的存储
这两个数据信息的存储跟 state 不一样,其不再是按照树形的结构去存储信息了,而是将其扁平化到 store 中去,不需要进行深层嵌套获取
action
modueleA: {
modueleAB : {
modueleABA
}
modueleAC:{
modueleACA
}
}
结果变成
js
store = {
"moduleA/action" : () => {}
"moduleA/modueleAB/action" : () => {}
"moduleA/modueleAB/modueleABA/action" : () => {}
"modueleAC/action" : () => {}
"modueleAC/modueleACA/action" : () => {}
}
- module 树的处理
通过判断是否存在子 module,然后不断深度遍历从而处理整个 module 树
命名空间维护
对于 Vuex 其命名空间的维护是比较重要的一点,通过判断是否存在 namespaced : true
去将整个 module 树转换成一个个 path 路径数组结构
例子
js
modueleA: {
namespaced: true,
modueleAB : {
namespaced: true,
modueleABA :{
namespaced: true,
},
modueleABB :{
namespaced: true,
}
},
modueleAC: {
namespaced: true,
modueleACA :{
namespaced: true,
},
modueleACB :{
namespaced: false,
}
}
}
其转换后的结构如下:
js
modueleA = "modueleA"
modueleAB = "modueleA/modueleAB"
modueleABA = "modueleA/modueleAB/modueleABA"
modueleABB = "modueleA/modueleAB/modueleABB"
modueleAC = "modueleA/modueleAC"
modueleACA = "modueleA/modueleAC/modueleACA"
modueleACB = "modueleA/modueleAC"
核心代码
js
// 根据 各模块的 namespace 形成模块的路径
/**
* [ 'a' , 'aa' , 'aaa'] 中 全有 namespaced:true ,
* [ 'a' , 'ab' , 'aba'] 中 'ab' 的 namespaced:false , a => "a"; 'ab' => 'a'; 'aba' => 'a/aba'
* @param {*} path
*/
function ModuleCollection.getNamespace(path) {
let module = this.root
return path.reduce((namespace, key) => {
module = module.getChild(key)
return namespace + (module.namespaced ? key + "/" : "")
}, "")
}
如上述所示 我们在组件中使用 action 的时候那就可以使用 this.$store.dispatch("modueleA/modueleAB/actionxx" , {})
去访问对应的 action 方法了,那么在 module 的 action 中我们去调用例外的 action 是否也需要全路径的方式去访问哪?对于这个问题 Vuex 是如何处理的
Vuex 对于组件中访问 和 module 访问其实提供了不同的方法,其实现如下
- 组件中访问 action
js
this.dispatch = function boundDispatch(type, payload) {
return dispatch.call(store, type, payload)
}
- module 中访问 action
js
// 根据namespaced提供其全路径的调用方法对象
const local = (module.context = makeLocalContext(store, namespace, path))
let res = handler.call(
store,
{
// 调用本地的 local
dispatch: local.dispatch,
commit: local.commit,
getters: local.getters,
state: local.state,
rootGetters: store.getters,
rootState: store.state,
},
payload,
cb
)
可见其核心方法就是 makeLocalContext
makeLocalContext
提供不需要全路径的方式去访问本地 module 其他 action,getter 的方法的功能
核心源码
js
function makeLocalContext(store, namespace, path) {
// 判断是否是根模块
const noNamespace = namespace === ""
const local = {
/*
生成根模块和各局部命名空间模块 的 dispatch方法,使得在局部命名空间模块 不需要全路径调用
也是提供我们在 模块action中
increment: ({ dispatch,commit,getters,state,rootGetters,rootState } , payload, cb ) => commit('increment')
dispatch去访问模块中其他的action
dispatch('rootAction',{ name:'xx'},{ root: false}) // root:true去访问全局的action
===
dispatch('a/ab/rootAction',{ name:'xx'},{ root: true})
*/
dispatch: noNamespace
? store.dispatch
: (_type, _payload, _options) => {
const args = unifyObjectStyle(_type, _payload, _options)
const { payload, options } = args
let { type } = args
if (!options || !options.root) {
type = namespace + type
if (process.env.NODE_ENV !== "production" && !store._actions[type]) {
console.error(`[vuex] unknown local action type: ${args.type}, global type: ${type}`)
return
}
}
return store.dispatch(type, payload)
},
/*
生成根模块和各局部命名空间模块 的 commit方法,使得在局部命名空间模块 不需要全路径调用
也是提供我们在 模块action中
increment: ({ dispatch,commit,getters,state,rootGetters,rootState } , payload, cb ) => commit('increment')
commit去访问各自模块中其他的mutation
commit('decrement',{ name:'xx'},{ root: false})
===
commit('a/ab/decrement',{ name:'xx'},{ root: true}) // root:true去访问全局的commit
*/
commit: noNamespace
? store.commit
: (_type, _payload, _options) => {
const args = unifyObjectStyle(_type, _payload, _options)
// 获取参数和配置
const { payload, options } = args
// 获取提交的路径
let { type } = args
// 判断是否 显式设置 commit 提交到根模块而不是当前模块 options.root : true
if (!options || !options.root) {
// 如果没有设置 root ,那么说明提交到当前模块,那么我们的 commit1 就需要添加他的命名路径 变成 'a/aa/aaa/commit1'
type = namespace + type
if (process.env.NODE_ENV !== "production" && !store._mutations[type]) {
console.error(`[vuex] unknown local mutation type: ${args.type}, global type: ${type}`)
return
}
}
// 还是调用store中 按照全路径使用
store.commit(type, payload, options)
},
}
// getters and state object must be gotten lazily
// because they will be changed by vm update
Object.defineProperties(local, {
getters: {
get: noNamespace ? () => store.getters : () => makeLocalGetters(store, namespace),
},
state: {
get: () => getNestedState(store.state, path),
},
})
return local
}