Vuex源码解析——API原理
上篇文章总结了Vuex初始化过程中涉及的原理,接下来从API层面切入来分析Vuex的状态管理运作流程。
状态管理核心
commit
在Vuex中有一个核心规则在于所有的状态变更都需要由mutation驱动,而mutation的调用则是通过store实例的接口commit驱动的。源码如下:
commit (type, payload, options) {
// check object-style commit
if (isObject(type) && type.type) {
options = payload
payload = type
type = type.type
}
const mutation = { type, payload }
const entry = this._mutations[type]
if (!entry) {
console.error(`[vuex] unknown mutation type: ${type}`)
return
}
this._withCommit(() => {
entry.forEach(function commitIterator (handler) {
handler(payload)
})
})
if (!options || !options.silent) {
this._subscribers.forEach(sub => sub(mutation, this.state))
}
}
该方法通过对type的判断去调用结构化时registerMutation方法注册的相对应的handler,并通过_withCommit方法装饰commit状态,最后判断是否为静默模式。如果非静默模式则遍历订阅者的回调。
subscribe
该方法也是实例store暴露的状态管理API之一,由以上源码可知,其目的在于监听mutation并做出相关动作。
dispatch
mutation和commitAPI对应,action则对应于dispatch。源码如下:
dispatch (type, payload) {
// check object-style dispatch
if (isObject(type) && type.type) {
payload = type
type = type.type
}
const entry = this._actions[type]
if (!entry) {
console.error(`[vuex] unknown action type: ${type}`)
return
}
return entry.length > 1
? Promise.all(entry.map(handler => handler(payload)))
: entry[0](payload)
}
可以看到该方法的逻辑和commit接口很相似,唯一区别在于action的handler返回值为promise,在该函数出口需要适配promise。
watch
该方法主要用来注册监听getter变化的回调。其实现原理是利用了Vuex内部的vm实例的$watch方法。接受参数如下:
watch(fn: Function, callback: Function, options?: Object): Function
fn函数接受state为第一个参数,getter为第二个参数,需要有返回值。watch监听该返回值,options和vm.$watch的options相同。该方法会返回一个函数,调用即可停止监听。
registerModule
Vuex在创建时就会完成store的结构塑造,但有时候我们需要在初始化完成后再动态改变store的结构。该方法就是用来动态注册新的模块。源码如下:
registerModule (path, module) {
if (typeof path === 'string') path = [path]
assert(Array.isArray(path), `module path must be a string or an Array.`)
this._runtimeModules[path.join('.')] = module
installModule(this, this.state, path, module)
// reset store to update getters...
resetStoreVM(this, this.state)
}
可以看到原理和之前讲解的初始化逻辑完全一样,通过重新调用installModule和resetStoreVM重置store。
unregisterModule
和动态注册模块对应的就是动态注销了,源码如下:
unregisterModule (path) {
if (typeof path === 'string') path = [path]
assert(Array.isArray(path), `module path must be a string or an Array.`)
delete this._runtimeModules[path.join('.')]
this._withCommit(() => {
const parentState = getNestedState(this.state, path.slice(0, -1))
Vue.delete(parentState, path[path.length - 1])
})
resetStore(this)
}
首先删除运行时对应模块,再通过commit装饰把当前state从父state上删除,最后调用resetStore重置store状态。
hotUpdate
该方法用来配合webpack的热重载API同步实现store的热重载。源码如下:
hotUpdate (newOptions) {
updateModule(this._options, newOptions)
resetStore(this)
}
function updateModule (targetModule, newModule) {
if (newModule.actions) {
targetModule.actions = newModule.actions
}
if (newModule.mutations) {
targetModule.mutations = newModule.mutations
}
if (newModule.getters) {
targetModule.getters = newModule.getters
}
if (newModule.modules) {
for (const key in newModule.modules) {
if (!(targetModule.modules && targetModule.modules[key])) {
console.warn(
`[vuex] trying to add a new module '${key}' on hot reloading, ` +
'manual reload is needed'
)
return
}
updateModule(targetModule.modules[key], newModule.modules[key])
}
}
}
可以看到其关键在于递归调用updateModule方法替换旧模块的actions,mutations和getters,再重置store。
辅助函数
Vuex提供了一系列语法糖方便操作store的各种属性。
mapState
该方法用于映射store中的state属性到组件的计算属性上。参数可以是对象或数组。源码如下:
export function mapState (states) {
const res = {}
normalizeMap(states).forEach(({ key, val }) => {
res[key] = function mappedState () {
return typeof val === 'function'
? val.call(this, this.$store.state, this.$store.getters)
: this.$store.state[val]
}
})
return res
}
function normalizeMap (map) {
return Array.isArray(map)
? map.map(key => ({ key, val: key }))
: Object.keys(map).map(key => ({ key, val: map[key] }))
}
首先使用normalizeMap方法将参数统一转化为格式为{key, val}的对象数组,接着遍历该数组构造一个新对象,该对象的key和参数key对应,每个key的value是由val转化而来的新计算属性函数:如果val是函数则传入store的state和getters调用映射为返回值,否则映射为store.state.val。
mapGetters
该方法和mapState很相似,用于映射getters属性到组件的计算属性上。不同的是normalize后的val不能是函数只能是字符串,因为getters本身就是函数了。源码如下:
export function mapGetters (getters) {
const res = {}
normalizeMap(getters).forEach(({ key, val }) => {
res[key] = function mappedGetter () {
if (!(val in this.$store.getters)) {
console.error(`[vuex] unknown getter: ${val}`)
}
return this.$store.getters[val]
}
})
return res
}
mapActions
该方法用于映射dispatch方法到组件的methods属性上。实现方法也类似,区别在于映射的不是计算属性而是普通的对象。
export function mapActions (actions) {
const res = {}
normalizeMap(actions).forEach(({ key, val }) => {
res[key] = function mappedAction (...args) {
return this.$store.dispatch.apply(this.$store, [val].concat(args))
}
})
return res
}
mapMutations
和mapActions完全一样,映射commit方法到methods属性上。
export function mapMutations (mutations) {
const res = {}
normalizeMap(mutations).forEach(({ key, val }) => {
res[key] = function mappedMutation (...args) {
return this.$store.commit.apply(this.$store, [val].concat(args))
}
})
return res
}
小结
以上就是Vuex最常用的API了,可以看到并没有什么复杂的东西,主要还是一些辅助和驱动操作,核心部分都是在Vuex的store初始化部分就已经构建完成了。
-- EOF --