对vuex源码进行了一个简单的梳理
store初始化的过程
- Vue.use(Vuex) 初始化的过程 调用install --> applyMixin --> Vue.mixin --> 往组件内this对象上挂载$store
const options = this.$options
if (options.store) {
// app.vue this上挂载$store对象
this.$store = options.store
} else if (options.parent && options.parent.$store) {
// 组件内的this对象上挂载$store对象
this.$store = options.parent.$store
}
2. new 一个Vuex.store的实例对象, 传入配置参数
const debug = process.env.NODE_ENV !== 'production'
const store = new Vuex.Store({
// 所有action的集合
actions,
// 所有gettrs的集合
getters,
// 对应的模块
modules: {
api
},
// 是否开启严格模式,线上会关掉,其余环境都会开启
strict: debug
})
- Store的constructor进行初始化,注册action, mutation
function installModule (store, rootState, path, module, hot) {
const isRoot = !path.length
// 当namespaced=true 的时候,其便成为命名空间模块。当模块被注册后,
// 它的所有 getter、action 及 mutation 都会自动根据模块注册的路径调整命名
const namespace = store._modules.getNamespace(path)
// register in namespace map
if (namespace) {
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)
})
}
// 如果namespace是‘’,那么makeLocalContext返回store原型上挂载的对应的dispatch, commit,
// 然后利用defineProperties,监听getter和state的改变
const local = module.context = makeLocalContext(store, namespace, path)
// 循环注册mutations
module.forEachMutation((mutation, key) => {
const namespacedType = namespace + key
registerMutation(store, namespacedType, mutation, local)
})
// 循环注册actions
module.forEachAction((action, key) => {
const namespacedType = namespace + key
registerAction(store, namespacedType, action, local)
})
// 循环注册getters
module.forEachGetter((getter, key) => {
const namespacedType = namespace + key
registerGetter(store, namespacedType, getter, local)
})
// 循环注册module
module.forEachChild((child, key) => {
installModule(store, rootState, path.concat(key), child, hot)
})
}
4.循环注册mutation
function registerMutation (store, type, handler, local) {
// 第一次进来的时候state还是undefined,等执行完 resetStoreVM这个方法以后才能够初始化,
// 其实state就是获取的this._vm._data.$$state
var entry = store._mutations[type] || (store._mutations[type] = []);
entry.push(function wrappedMutationHandler (payload) {
// 调用mutation对应的函数,第一个参数默认传入local.state,
// 如果只是单纯的从上下文来看,这个时候的local.state是指的全局的state,
// 但是使用的时候,state就是指的当前这个module下的state,是因为在installModule方法
// 最后一行重新调用installModule, 这个时候传入的path有值,
// 相当于每一个module都在本地维护一个自己的local.state,
//这个local.state只是当前模块的state
handler(local.state, payload);
});
}
5.action为什么第一个参数是store对象
function registerAction (store, type, handler, local) {
const entry = store._actions[type] || (store._actions[type] = [])
entry.push(function wrappedActionHandler (payload, cb) {
let res = handler({
dispatch: local.dispatch,
commit: local.commit,
getters: local.getters,
state: local.state,
rootGetters: store.getters,
rootState: store.state
}, payload, cb)
if (!isPromise(res)) {
res = Promise.resolve(res)
}
if (store._devtoolHook) {
return res.catch(err => {
store._devtoolHook.emit('vuex:error', err)
throw err
})
} else {
return res
}
})
}
handler代表的是你定义的action函数,第一个参数是在注册的时候自动创建一个对象,里面包含上述参数, vuex的action肯定返回一个promise对象回去
6.getters是怎么注册的
调用getters的方法目前有两种方式: 1).直接通过store.getters
function resetStoreVM (store, state, hot) {
const oldVm = store._vm
// bind store public getters
store.getters = {}
// _wrappedGetters是配置的所有getters
const wrappedGetters = store._wrappedGetters
const computed = {}
forEachValue(wrappedGetters, (fn, key) => {
// fn代表的是你定义的getters函数
computed[key] = () => fn(store)
Object.defineProperty(store.getters, key, {
// 获取对应的getters的时候直接把挂载到vue实例对象上的方法返回
get: () => store._vm[key],
enumerable: true // for local getters
})
})
store._vm = new Vue({
data: {
$$state: state
},
computed
})
// 开启严格模式,禁止用户直接修改vuex的数据
// 开启严格模式以后,每次修改state的时候,发送commit的时
// 会把store._commiting改为true, 调用完成mutation以后再改为false
// 当state的数据改变的时候,会先判断commiting的状态
// 如果为false的话,直接throw new Error出来
// 如果开启严格模式,那么就不能在mutation里面封装异步函数
// 这种情况会判定为手动修改state的值
if (store.strict) {
enableStrictMode(store)
}
// 如果内存中已经存在oldVm删掉->避免频繁更新或者注册,导致内存中出现多个oldVm
if (oldVm) {
if (hot) {
// dispatch changes in all subscribed watchers
// to force getter re-evaluation for hot reloading.
store._withCommit(() => {
oldVm._data.$$state = null
})
}
Vue.nextTick(() => oldVm.$destroy())
}
}
2017-8-28 update 关于为什么要把state传到vue的实例里面,我的理解是利用vue封装好的defineProperty+pub/sub的类,监听数据的改变,触发对应的watchers。并且页面中获取state值的方法都是直接或者间接从store._vm._data.$$state.state 这种方式获取的, 然后当数据更改的时候就能触发对应字段的set方法,然后通知各个watcher
2).在动态计算属性里面配置
这里借助的是mapGetters方法
export const mapGetters = normalizeNamespace((namespace, getters) => {
const res = {}
normalizeMap(getters).forEach(({ key, val }) => {
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
})
其实本质上也是借助于this.$store.getters方法,返回对应的函数回去,动态计算属性会执行这个函数,然后return回去对应的state
7.dispatch
dispatch (_type, _payload) {
// check object-style dispatch
const {
type,
payload
} = unifyObjectStyle(_type, _payload)
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)
}
同一个type对应1个函数的时候直接执行,把payload参数传进去。当对应两个或者以上的函数时,是用的promise.all方法进行调用的
8.vuex插件
合并devtool和自定义插件,然后循环调用,把this(store)传入插件内
plugins.concat(devtoolPlugin).forEach(plugin => plugin(this))
一般插件都是这么定义的
const myPlugin = store => {
// 当 store 初始化后调用
store.subscribe((mutation, state) => {
// 每次 mutation 之后调用
// mutation 的格式为 { type, payload }
})
}
初始化调用就不多说啦,那么store.subscribe怎么实现,每次commit的时候,就触发该方法
subscribe (fn) {
const subs = this._subscribers
if (subs.indexOf(fn) < 0) {
subs.push(fn)
}
return () => {
const i = subs.indexOf(fn)
if (i > -1) {
subs.splice(i, 1)
}
}
}
首先加载插件的时候就会执行subscribe方法,然后把回调函数存储到_subscribers数组内,然后在commit的方法内,当mutation回调函数执行完成以后,循环执行存储到_subscribers数组内的方法
store.js
method: commit
this._subscribers.forEach(sub => sub(mutation, this.state))
总结
- 经过对vuex源码的分析,更加深入的了解了vuex执行原理
- 经过查看vuex.esm.js对对象继承,this执行等js基础执行,有了更加深入的了解
- 文章中如果有所纰漏,敬请指出
- 下一步是看vue的源码,不过vue相关文章应该会分章节,分模块的写,不会一次写完,敬请期待