vuex
vuex和vue-router一样,也是通过插件的方式进行引入的。因为开发中,一般都会使用modules功能将store进行模块拆分,所以例子就通过这种方式进行。
Vue.use(Vuex)
const moduleA = {
...
}
const moduleB = {
...
}
const store = new Vuex.Store({
modules: {
a: moduleA,
b: moduleB
}
})
const vm = new Vue({
el: '#app',
render (h) {
return h(App)
},
store
})
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
我们先看index.js,这个很简单,导出了相关方法,所以关于定义只需要看store.js就好。在install方法中执行了applyMixin,其中就是执行了Vue.mixin({ beforeCreate: vuexInit })。 而vuexInit方法,就是将传入的store实例赋值给了this.$store
function vuexInit () {
const options = this.$options
// store injection
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
}
}
2
3
4
5
6
7
8
9
10
11
注册流程就这样非常简单
实例化
在Store构造函数中,主要是执行options的解析,也就是执行this._modules = new ModuleCollection(options),实例化ModuleCollection执行了this.register([], rawRootModule, false) 我们来看看register方法。
register (path, rawModule, runtime = true) {
if (__DEV__) {
assertRawModule(path, rawModule)
}
const newModule = new Module(rawModule, runtime)
if (path.length === 0) {
this.root = newModule
} else {
const parent = this.get(path.slice(0, -1))
parent.addChild(path[path.length - 1], newModule)
}
if (rawModule.modules) {
forEachValue(rawModule.modules, (rawChildModule, key) => {
this.register(path.concat(key), rawChildModule, runtime)
})
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
该方法首先将options通过new Module存放到root,相当于给options存了一个副本。如果用户使用了module就通过循环再次注册这时候会吧module放到_children下面 这时候root的值为
this.root = {
state: {},
_children: {
a: Module,
b: Module
},
_rawModule: {
modules: {
a: {},
b: {}
}
}
}
2
3
4
5
6
7
8
9
10
11
12
13
之后就是核心installModule,这个方法主要注册了mutation、action、getter、module.
function installModule (store, rootState, path, module, hot) {
const isRoot = !path.length
const namespace = store._modules.getNamespace(path)
if (module.namespaced) {
store._modulesNamespaceMap[namespace] = module
}
// set state
if (!isRoot && !hot) {
const parentState = getNestedState(rootState, path.slice(0, -1))
const moduleName = path[path.length - 1]
store._withCommit(() => {
Vue.set(parentState, moduleName, module.state)
})
}
// 为了保证不同module可以定义相当名字的函数,vuex给函数名加上了namespaced
const local = (module.context = makeLocalContext(store, namespace, path))
module.forEachMutation((mutation, key) => {
const namespacedType = namespace + key
registerMutation(store, namespacedType, mutation, local)
})
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.forEachChild((child, key) => {
installModule(store, rootState, path.concat(key), child, hot)
})
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
local
const local = (module.context = makeLocalContext(store, namespace, path))
local的核心是根据namespace定义了dispatch和commit方法。并给getters和state属性做了响应式处理。
state 初始化
在state的初始化过程中,首次没什么好说的,这个主要作用在于处理Module,这时候!!root和!hot都为true。如果在options中定义了state那么 parentState就是它的值,_withCommit是一个锁,当有回调函数在执行的时候,别的回调函数就无法执行。在回调函数中调用了Vue.set,将模块中的属性放到了 主模块中,并且用set使其响应式
const parentState = {
d: 0
}
// 设置A模块的state
Vue.set(parentState, 'a', { count: 0 })
// 设置B模块的state
Vue.set(parentState, 'b', { count: 0 })
2
3
4
5
6
7
通过namespace的方法,即使模块中属性名字相同也不会冲突。
更重要的是我们知道,state修改会触发组件重新渲染,是响应式的。其实关于响应式的定义,在resetStoreVM方法中
store._vm = new Vue({
data: {
$state: state
},
})
2
3
4
5
这个传入的state就是初始化好的state。
Mutation 初始化
module.forEachMutation(function (mutation, key) {
var namespacedType = namespace + key
registerMutation(store, namespacedType, mutation, local)
})
2
3
4
看上面这段代码,通过module的循环,拿到key,然后组装成namespacedType,对于上面的例子
namespacedType = 'a/increment'
然后看registerMutation方法,给store._mutations数组中添加了一个wrappedMutationHandler方法,最终会执行传入的mutation 也就是说最后会被组和成
store = {
_mutations: {
'a/increment': [ƒ]
}
}
2
3
4
5
Action 初始化
在循环阶段和mutation是类似的,不过因为action是支持异步的,所以在注册上有所不同
function registerAction (store, type, handler, local) {
const entry = store._actions[type] || (store._actions[type] = [])
entry.push(function wrappedActionHandler (payload) {
let 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)
}
return res
})
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
可以看到结果用Promsie.resolve进行包裹,且他可以获取的入参更多。最终获取的是
store = {
_actions: {
'a/increment': [ƒ]
}
}
2
3
4
5
Getters初始化和响应式
循环也是一样就不说了,但是注册会有很大不同
function registerGetter (store, type, rawGetter, local) {
if (store._wrappedGetters[type]) {
if (__DEV__) {
console.error(`[vuex] duplicate getter key: ${type}`)
}
return
}
store._wrappedGetters[type] = function wrappedGetter (store) {
return rawGetter(
local.state, // local state
local.getters, // local getters
store.state, // root state
store.getters // root getters
)
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
有意思的是,我们在这里可以获取四个参数。并且我们在查看resetStoreVM的时候可以看到,它将_wrappedGetters转换成了computed放到了store._vm实例化的vue的computed中实现响应式, 并且对store.getters进行了拦截,响应到了store._vm
使用
vuex 辅助函数
normalizeNamespace函数就是获取当前传入的参数中是否具有namespace字段,并且序列化成/x。 normalizeMap 就是吧对象和数组两种形式都转换成对象的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 } ]
2
state的使用和mapState
在我们的使用过程中,最终其实是调用的computed,computed会调用 watcher.evaluate()执行到this.getter.call(vm, vm),那也就是定义返回的mapState函数
function mappedState () {
let state = this.$store.state
let getters = this.$store.getters
if (namespace) {
const module = getModuleByNamespace(this.$store, 'mapState', namespace)
if (!module) {
return
}
state = module.context.state
getters = module.context.getters
}
return typeof val === 'function'
? val.call(this, state, getters)
: state[val]
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
因为是模块内,module.context其实就是之前定义的makeLocalContext(store, namespace, path),在这里面有个state和 getters拦截器。这里会触发依赖收集,并且最终返回state[val]
getter 和 mapGetters 的使用
这个其实和state是一样的,state因为只是一个值,或者有可能是一个方法,所以它的返回需要包裹,但是getters必须是一个方法, 所以它只要正确的返回key对应的val给computed去执行就可以了,所以它的mapGetters很简单 不赘述
mutations 、actions 和 mapMutations 、mapActions的使用
function mappedMutation (...args) {
let commit = this.$store.commit
if (namespace) {
const module = getModuleByNamespace(this.$store, 'mapMutations', namespace)
if (!module) {
return
}
commit = module.context.commit
}
return typeof val === 'function'
? val.apply(this, [commit].concat(args))
: commit.apply(this.$store, [val].concat(args))
}
2
3
4
5
6
7
8
9
10
11
12
13
和前面类似,如果是module就调用local中的 commit方法,如果不是就调用构造函数中申明的commit。在local的commit就是通过 namespace拿到对应的发行,最终还是调用commit函数
action和mutation是类似的,不过actions调用的是 dispatch函数,最后返回了一个Promise。然后交给commit执行。