# 初始化代码分析
# Vue.use(Vuex)
分析代码可知最终导出的对象中有个install键,其值就是mixin.js中的方法,需要运行该方法
const version = Number(Vue.version.split('.')[0])
if (version >= 2) {
//因为2版本才提供了beforeCreate这个钩子函数
Vue.mixin({ beforeCreate: vuexInit })
} else {
// override init and inject vuex init procedure
// for 1.x backwards compatibility.
const _init = Vue.prototype._init
Vue.prototype._init = function (options = {}) {
options.init = options.init
? [vuexInit].concat(options.init)
: vuexInit
_init.call(this, options)
}
}
function vuexInit () {
const options = this.$options
// store注入,并挂载到$store
if (options.store) {
this.$store = typeof options.store === 'function'
? options.store()
: options.store
} else if (options.parent && options.parent.$store) {
// parent参数指定已创建的实例之父实例,在两者之间建立父子关系。子实例可以用 this.$parent 访问父
// 实例,子实例被推入父实例的 $children 数组中
this.$store = options.parent.$store
}
}
# store初始化
使用store = new Vuex.Store({})初始化store,可以看下store.js中的constructor
constructor (options = {}) {
// 这个是在开发过程中的一些环节判断,vuex要求在创建vuex store实例之前必须先使用这个方法Vue.use(Vuex)来安
// 装vuex,项目必须也得支持promise,store也必须通过new来创建实例
if (process.env.NODE_ENV !== 'production') {
assert(Vue, `must call Vue.use(Vuex) before creating a store instance.`)
assert(typeof Promise !== 'undefined', `vuex requires a Promise polyfill in.`)
assert(this instanceof Store, `Store must be called with the new operator.`)
}
// 从参数options中结构出相关变量
const {
plugins = [],
strict = false
} = options
// 初始化store内部状态,Object.create(null)可以创建一个干净的空对象
// 提交状态的标志,在_withCommit中,当使用mutation时,会先赋值为true,再执行mutation,修
// 改state后再赋值为false,在这个过程中,会用watch监听state的变化时是否_committing为true,从
// 而保证只能通过mutation来修改state
this._committing = false
// 这里面的函数运行都是返回的promise
this._actions = Object.create(null)
this._actionSubscribers = []
// 保存所有的mutions,格式如
/**
moduleA/key1:[function(){}],
moduleA/key2:[function(){}],
moduleB/key1:[function(){}],
**/
this._mutations = Object.create(null)
//用于保存包装后的getter
this._wrappedGetters = Object.create(null)
// vuex支持模块,即将state通过key-value的形式拆分为多个模块
this._modules = new ModuleCollection(options)
//// 用于保存namespaced的模块
this._modulesNamespaceMap = Object.create(null)
//// 用于监听mutation
this._subscribers = []
//// 用于响应式地监测一个 getter 方法的返回值
this._watcherVM = new Vue()
this._makeLocalGettersCache = Object.create(null)
// bind commit and dispatch to self
const store = this
// 缓存dispatch和commit方法
const { dispatch, commit } = this
// 定义dispatch方法
this.dispatch = function boundDispatch (type, payload) {
return dispatch.call(store, type, payload)
}
// 定义commit方法
this.commit = function boundCommit (type, payload, options) {
return commit.call(store, type, payload, options)
}
// 定义严格模式,不要在发布环境下启用严格模式!严格模式会深度监测状态树来检测不合规的状态变更
// 请确保在发布环境下关闭严格模式,以避免性能损失,具体后续enableStrictMode方法会提到
this.strict = strict
const state = this._modules.root.state
// 初始化根模块,递归注册子模块,收集getter
installModule(this, state, [], this._modules.root)
//初始化store中的state,使得state变成响应式的,原理就是将state作为一个vue实例的data属性传入
resetStoreVM(this, state)
// 执行插件,这个是一个数组,所以遍历他,然后执行每个插件的函数
plugins.forEach(plugin => plugin(this))
// 调试工具使用,实现时光回退等功能
const useDevtools = options.devtools !== undefined ?
options.devtools : Vue.config.devtools
if (useDevtools) {
devtoolPlugin(this)
}
}
接下来我们看下核心的模块安装的方法installModule(this, state, [], this._modules.root)
function installModule (store, rootState, path, module, hot) {
const isRoot = !path.length
const namespace = store._modules.getNamespace(path)
/*
* {
* // ...
* modules: {
* moduleA: {
* namespaced: true
* },
* moduleB: {}
* }
* }
* moduleA的namespace -> 'moduleA/'
* moduleB的namespace -> ''
*/
if (module.namespaced) {
// 保存namespaced模块
store._modulesNamespaceMap[namespace] = module
}
// 非根组件设置state
if (!isRoot && !hot) {
// 根据path获取父state
const parentState = getNestedState(rootState, path.slice(0, -1))
const moduleName = path[path.length - 1]
store._withCommit(() => {
// 使用Vue.set将state设置为响应式
Vue.set(parentState, moduleName, module.state)
})
}
// 设置module的上下文,从而保证mutation和action的第一个参数能拿到对应的state getter等
// make localized dispatch, commit, getters and state
// if there is no namespace, just use root ones
const local = module.context = makeLocalContext(store, namespace, path)
// 逐一注册mutation
module.forEachMutation((mutation, key) => {
const namespacedType = namespace + key
// 保存到全局_mutations
registerMutation(store, namespacedType, mutation, local)
})
// 逐一注册action, 保存到全局的store的_actions上面,格式和mutions一样
module.forEachAction((action, key) => {
const type = action.root ? key : namespace + key
const handler = action.handler || action
// 该方法如果会对相应函数的返回值做promise处理
registerAction(store, type, handler, local)
})
// 逐一注册getter,保存到store的_wrappedGetters上面格式如
/**
moduleA/key1:function wrappedGetter(store){},
moduleA/key2:function wrappedGetter(store){},
moduleB/key1:function wrappedGetter(store){},
**/
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)
})
}
// 可以简单看下registerAction方法
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)
// 对返回值如果不是promise则用promise包裹
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
}
})
}
上面的分析中有个makeLocalContext方法来设置module的上下文,绑定对应的dispatch、commit、getters、state
function makeLocalContext (store, namespace, path) {
const noNamespace = namespace === ''
const local = {
// 如果没有namespace,直接使用原来的
dispatch: noNamespace ? store.dispatch : (_type, _payload, _options) => {
// 统一格式 因为支持payload风格和对象风格
const args = unifyObjectStyle(_type, _payload, _options)
const { payload, options } = args
let { type } = args
// 如果参数传了{root: true}不会加上namespace 即在命名空间模块里提交根的 action
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: noNamespace ? store.commit : (_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._mutations[type]) {
console.error(`[vuex] unknown local mutation type: ${args.type}, global type: ${type}`)
return
}
}
// 触发mutation
store.commit(type, payload, options)
}
}
// getters and state object must be gotten lazily
// because they will be changed by vm update
// 这里的getters和state需要延迟处理,需要等数据更新后才进行计算,所
// 以使用getter函数,当访问的时候再进行一次计算
Object.defineProperties(local, {
getters: {
get: noNamespace
? () => store.getters
: () => makeLocalGetters(store, namespace)
},
state: {
get: () => getNestedState(store.state, path)
}
})
return local
}
初始化store中的state,使得state变成响应式的,原理就是将state作为一个vue实例的data属性传入
function resetStoreVM (store, state, hot) {
// 保存原有store的_vm
const oldVm = store._vm
// bind store public getters
store.getters = {}
// reset local getters cache
store._makeLocalGettersCache = Object.create(null)
const wrappedGetters = store._wrappedGetters
const computed = {}
// 遍历这个对象,获取每个getter的key和对应的方法
forEachValue(wrappedGetters, (fn, key) => {
computed[key] = partial(fn, store)
// 将getter以key-value的形式缓存在变量computed中,其实后面就是将getter作为vue实例中的计算属性
Object.defineProperty(store.getters, key, {
get: () => store._vm[key],
enumerable: true // for local getters
})
})
// silent设置为true,则取消了所有的警告和日志,眼不见为净
const silent = Vue.config.silent
Vue.config.silent = true
// 将传入的state,作为vue实例中的data的$$state属性,将刚刚使用computed变量搜集的getter,作为
// 实例的计算属性,所以当state和getter都变成了响应式的了
store._vm = new Vue({
data: {
$$state: state
},
computed
})
Vue.config.silent = silent
//如果设置了严格模式则,不允许用户在使用mutation以外的方式去修改state
if (store.strict) {
// 就是通过$watch去深度监听state的值
enableStrictMode(store)
}
if (oldVm) {
if (hot) {
// 将原有的vm中的state设置为空,所以原有的getter都会重新计算一遍,利用的就是vue中的响应式,getter作
// 为computed属性,只有他的依赖改变了,才会重新计算,而现在把state设置为null,所以计算属性重新计算
store._withCommit(() => {
oldVm._data.$$state = null
})
}
// 在下一次周期销毁实例
Vue.nextTick(() => oldVm.$destroy())
}
}