五、Api解析(一)
假设我们的结构是这样的
const modulesA = {
namespaced: true,
state: {
count: 1
},
getters: {
computedConut(state) {
return state.count + 1
}
},
mutations: {
add(state,num) {
state.count += num
}
},
actions: {
addCount(context) {
context.commit('add', 2)
}
}
}
const store = new Vuex.Store({
modules: {
modulesA
}
})
1.state
假如我们想访问modulesA中state的属性count,官方文档给出的api是使用this.$store.state.modulesA.count。当访问到state的时候会触发Store实例的state的get函数,他会返回this._vm._data.$$state,之前提到过,store._vm在执行resetStoreVM时,会通过new Vue进行初始化,data中的$$state对应的是定义的state。state是resetStoreVM接受的第二个参数,也就是this._modules.root.state。在最初this._modules.root.state只代表根部module的state,并没有形成一个嵌套的结构,那么state tree的形成是在子模块执行installModule函数的时候,会通过 Vue.set(parentState, moduleName, module.state),往root.state中,以模块的key作为key,模块的state作为value进行添加。也就是说最终的$$state: state对应的state是一个树状结构的state,这样当我们就可以通过state.modulesA.count拿到modulesA模块中的count。如果我们对state进行直接修改,比如this.$store.state.moudlesA.count = 4,那么并不会成功的修改state,因为state的set函数他并不会去做相应的state的修改,而是会在开发模式下报出一个警告。
// src/store.js
export class Store {
constructor (options = {}) {
...
this._modules = new ModuleCollection(options)
...
const state = this._modules.root.state
// init root module.
// this also recursively registers all sub-modules
// and collects all module getters inside this._wrappedGetters
installModule(this, state, [], this._modules.root)
// initialize the store vm, which is responsible for the reactivity
// (also registers _wrappedGetters as computed properties)
resetStoreVM(this, state)
...
}
}
// src/store.js
function installModule (store, rootState, path, module, hot) {
...
// set state
if (!isRoot && !hot) {
// 拿到父级的state
const parentState = getNestedState(rootState, path.slice(0, -1))
// 拿到模块名称
const moduleName = path[path.length - 1]
store._withCommit(() => {
...
// 去设置当前的state到父级state中
Vue.set(parentState, moduleName, module.state)
})
}
...
}
2.getters
假如我们想访问modulesA的getters中的computedConut,官方文档给出的api是使用this.$store.getters['modulesA/computedConut']。getter的初始化同样是在resetStoreVM函数中,首先会定义一个空的computed对象,然后遍历store._wrappedGetters(在执行installModule函数的时候会执行registerGetter函数,在store._wrappedGetters挂载我们定义的getter函数),把getters函数作为值,getter函数的key作为computed的key,注册在computed,在遍历的时候还会通过Object.defineProperty定义了在访问store.getters.xxx的访问,最终会访问到store._vm[key],也就是会访问到store的vm实例上,为什么这么做,是因为接着,他会把computed作为vm实例的computed,这样通过访问this.$store.getters.xxx就会被代理到了store._vm[key]也就是我们经过vue实例computed处理过的具体的getters函数。
// src/store.js
function resetStoreVM (store, state, hot) {
...
const wrappedGetters = store._wrappedGetters
const computed = {}
forEachValue(wrappedGetters, (fn, key) => {
// use computed to leverage its lazy-caching mechanism
// direct inline function use will lead to closure preserving oldVm.
// using partial to return function with only arguments preserved in closure environment.
computed[key] = partial(fn, store)
Object.defineProperty(store.getters, key, {
get: () => store._vm[key],
enumerable: true // for local getters
})
})
// use a Vue instance to store the state tree
// suppress warnings just in case the user has added
// some funky global mixins
...
store._vm = new Vue({
data: {
$$state: state
},
computed
})
...
}
3.commit
假如我们想调用modulesA的mutations中的add函数,来修改我们的state,那么需要执行this.$store.commit('modulesA/add')。store中的commit函数首先会通过unifyObjectStyle函数解析我们传入的三个参数,也就是文档中支持两种传入方式
- commit(type: string, payload?: any, options?: Object)
- commit(mutation: Object, options?: Object)
最终拿到三个参数,第一个是type也就是我们需要调用的mutations的名称,第二个参数是,payload,也就是我们需要传入所调用mutations的参数,第三个参数是options(在actions中提交commit可以传入root:true,让context访问到根部module)。接着commit函数会通过this._mutations[type](在registerMutation函数中通过)拿到对应的mutations函数,然后在_withCommit函数的包裹下,遍历执行(因为mutations在注册中会注册为一个数组),并把payload作为参数传入。为什么用_withCommit函数进行包裹,_withCommit函数帮我们做了这样一件事他首先把全局的_committing置为了true,在执行完其中的函数,在把他置为false,在函数resetStoreVM的执行中,如果传的strict是true,则会执行enableStrictMode函数,enableStrictMode函数的目的是通过vm的
$watch方法对this._data.$$state进行了监听,当修改的时候,如果他判断_committing为false,则会报错。也就是说如果通过mutations进行了state的修改,那么是不会报错的,如果我们擅自进行了修改,则会报错。传入root为true可以调用到根部commit的原因是在installModule函数中执行makeLocalContext函数时,他定义了namespaced的module下的commit会接受第三个参数,如果第三个参数(options)中有root为true,那么在调用commit的时候,传入的type就是原始定义的type,而不是和namespace拼接之后的type。
// src/store.js
commit (_type, _payload, _options) {
// check object-style commit
const {
type,
payload,
options
} = unifyObjectStyle(_type, _payload, _options)
const mutation = { type, payload }
const entry = this._mutations[type]
if (!entry) {
...
}
this._withCommit(() => {
entry.forEach(function commitIterator (handler) {
handler(payload)
})
})
...
}
// src/store.js
_withCommit (fn) {
const committing = this._committing
this._committing = true
fn()
this._committing = committing
}
// src/store.js
function enableStrictMode (store) {
store._vm.$watch(function () { return this._data.$$state }, () => {
if (__DEV__) {
assert(store._committing, `do not mutate vuex store state outside mutation handlers.`)
}
}, { deep: true, sync: true })
}
4.dispatch
假如我们想调用modulesA的actions中的addCount函数,来提交提个mutation函数,那么需要执行this.$store.dispatch('modulesA/addCount')。store中的dispatch函数和commit函数相似,首先通过unifyObjectStyle对传入的参数进行解析,拿到type和payload参数。同样也会去this._actions[type]中拿到对应的actions中注册的函数。和commit不同的是,他并不会直接执行,而是会先判断判断拿到的_actions[type]的length,如果是1则会执行,如果不是1,则会执行 Promise.all(entry.map(handler => handler(payload))),这是因为actions在注册的时候会通过registerAction函数进行注册,registerAction函数中会判断传入的actions是否是一个promise如果不是promise,则会通过res = Promise.resolve(res),把他变成一个promise,dispatch函数最终会返回一个promise,其中的reslove的执行时机,正是执行entrypromise的then的时候。也就是说,我们调用dispatch最终会返回一个promise,这个promise触发then的时机,是对应的所有actions执行完的时候。其实dispatch是一个异步的函数,他可以接受一些异步的方法,最终提交mutation来修改state,dispatch很好的帮助我们规范了提交mutations的方式。
// src/store.js
dispatch (_type, _payload) {
// check object-style dispatch
const {
type,
payload
} = unifyObjectStyle(_type, _payload)
const action = { type, payload }
const entry = this._actions[type]
...
const result = entry.length > 1
? Promise.all(entry.map(handler => handler(payload)))
: entry[0](payload)
return new Promise((resolve, reject) => {
result.then(res => {
...
resolve(res)
}, error => {
...
reject(error)
})
})
}
// src/store.js
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)
}
if (store._devtoolHook) {
...
} else {
return res
}
})
}