Vuex源码解析
Vuex的注册
在文件中引入Vue,使用vue中的use方法进行注册
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
src/store.js
let Vue; // bind on install
export function install (_Vue) {
if (Vue && _Vue === Vue) {
if (__DEV__) {
console.error(
'[vuex] already installed. Vue.use(Vuex) should be called only once.'
)
}
return
}
Vue = _Vue // 通过Vue.use(Vuex)将Vue传进来,不需要额外引入vue
applyMixin(Vue)
}
首先声明了Vue,在install方法中会被赋值,这样写可以不用引入import Vue from 'vue'
,避免在打包的时候一同将vue进行打包。
applyMixin()
export default function (Vue) {
const version = Number(Vue.version.split('.')[0])
if (version >= 2) {
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)
}
}
/**
* Vuex init hook, injected into each instances init hooks list.
*/
function vuexInit () {
const options = this.$options // 获取组件上的所有options
// store injection
if (options.store) { // 判断组件是否存在store
this.$store = typeof options.store === 'function'
? options.store()
: options.store
} else if (options.parent && options.parent.$store) { // 从父组件中获取store
this.$store = options.parent.$store
}
}
}
在applyMixin方法中会区分vue的版本,1.x跟2.x的钩子函数不一样。在vue实例中加上store后,根组件就可以取得store,其他子组件通过其父组件获取store,逐层传递,最后所有的组件都存在$store属性了。
Store构造函数
定义了state、mutations、actions等,
先来看看Store的contructor中的属性
export class Store {
constructor (options = {}) {
// 如果是通过script标签直接引入vuex,则不需要调用Vue.use(Vuex),自动安装
if (!Vue && typeof window !== 'undefined' && window.Vue) {
install(window.Vue)
}
// 定义plugins和strict属性
const {
plugins = [],
strict = false
} = options
// 定义内部属性
// 提交状态的标志,在_withCommit中,当使用mutation是,会赋值为true,再执行mutation,修改state
// 后再赋值为false,在这个过程中,会用watch监听state的变化是否_committing为true,从而保证了state
// 只能通过mutation来改变
this._committing = false
// 用于保存所有的action
this._actions = Object.create(null)
// 用于保存订阅action的回调
this._actionSubscribers = []
// 保存所有的mutations
this._mutations = Object.create(null)
// 保存包装后的getters
this._wrappedGetters = Object.create(null)
// 收集module
this._modules = new ModuleCollection(options)
// 保存namespace模块
this._modulesNamespaceMap = Object.create(null)
// 订阅,用于监听mutation
this._subscribers = []
// vue实例,用于watch监听变化
this._watcherVM = new Vue()
this._makeLocalGettersCache = Object.create(null)
// 绑定commit和dispatch到store实例中
const store = this
const { dispatch, commit } = this
this.dispatch = function boundDispatch (type, payload) {
return dispatch.call(store, type, payload)
}
this.commit = function boundCommit (type, payload, options) {
return commit.call(store, type, payload, options)
}
// 在严格模式下,state只能通过mutations进行修改
this.strict = strict
const state = this._modules.root.state
// 安装模块,用于处理module、action、mutation、getters等
installModule(this, state, [], this._modules.root)
// 重置state、getter等(采用vue实例)
resetStoreVM(this, state)
// 插件的注册
plugins.forEach(plugin => plugin(this))
const useDevtools = options.devtools !== undefined ? options.devtools : Vue.config.devtools
if (useDevtools) {
devtoolPlugin(this)
}
}
}
moduleCollection()
src/module/module-collection.js
export default class ModuleCollection {
constructor (rawRootModule) {
// register root module (Vuex.Store options)
// 注册根模块(根模块的path是 [] )
this.register([], rawRootModule, false)
}
get (path) {
// 根据path获取module
return path.reduce((module, key) => {
return module.getChild(key)
}, this.root)
}
register (path, rawModule, runtime = true) {
if (__DEV__) {
assertRawModule(path, rawModule)
}
// 创建一个module实例
const newModule = new Module(rawModule, runtime)
if (path.length === 0) {
// 将根module实例绑定在root属性上
this.root = newModule
} else {
// 把子module添加到父module的_children属性上
const parent = this.get(path.slice(0, -1))
parent.addChild(path[path.length - 1], newModule)
}
// register nested modules
// 注册成一个树
if (rawModule.modules) {
forEachValue(rawModule.modules, (rawChildModule, key) => {
this.register(path.concat(key), rawChildModule, runtime)
})
}
}
unregister (path) {
const parent = this.get(path.slice(0, -1))
const key = path[path.length - 1]
const child = parent.getChild(key)
if (!child) {
if (__DEV__) {
console.warn(
`[vuex] trying to unregister module '${key}', which is ` +
`not registered`
)
}
return
}
if (!child.runtime) {
return
}
parent.removeChild(key)
}
}
模块例子
{
modules:{
m1:{
state:{}
},
m2:{
state:{}
},
}
}
子模块的path就是key,m1的path就是[‘m1’],m2的path就是[‘m2’]
module()
export default class Module {
constructor (rawModule, runtime) {
this.runtime = runtime
// 存储子模块
this._children = Object.create(null)
// Store the origin module object which passed by programmer
// 保存原module,在store的installModule中处理actions、mutations
this._rawModule = rawModule
const rawState = rawModule.state
// Store the origin module's state
// 保存state
this.state = (typeof rawState === 'function' ? rawState() : rawState) || {}
}
addChild (key, module) {
this._children[key] = module
}
}
installModule()
用于处理根module、命名空间、action、mutation、getters和递归注册子module。
将module的namespace保存到store._modulesNamespaceMap中,判断是否为根module以及hot,获取对应的state(不是根module就获取父module的state)然后调用Vue.set方法将当前的state挂载到父state上,最后去设置module的上下文注册mutations等
function installModule (store, rootState, path, module, hot) {
const isRoot = !path.length
const namespace = store._modules.getNamespace(path)
// 在namespace映射中注册
if (module.namespaced) {
if (store._modulesNamespaceMap[namespace] && __DEV__) {
console.error(`[vuex] duplicate namespace ${namespace} for the namespaced module ${path.join('/')}`)
}
// 保存namespaced模块
store._modulesNamespaceMap[namespace] = module
}
// 响应化
if (!isRoot && !hot) {
// 如果不是根module,就获取父state
const parentState = getNestedState(rootState, path.slice(0, -1))
// 获取当前module
const moduleName = path[path.length - 1]
store._withCommit(() => {
if (__DEV__) {
if (moduleName in parentState) {
console.warn(
`[vuex] state field "${moduleName}" was overridden by a module with the same name at "${path.join('.')}"`
)
}
}
// 将当前module的state设置成响应式
Vue.set(parentState, moduleName, module.state)
})
}
// 设置module上下文,(context)
const local = module.context = makeLocalContext(store, namespace, path)
// 注册mutation
module.forEachMutation((mutation, key) => {
const namespacedType = namespace + key
registerMutation(store, namespacedType, mutation, local)
})
// 注册action
module.forEachAction((action, key) => {
const type = action.root ? key : namespace + key
const handler = action.handler || action
registerAction(store, type, handler, local)
})
// 注册getter
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)
})
}
makeLocalContext()
通过判断namespace是否存在,不存在就使用原来store中dispatch跟commit,否则就使用module上的store。存在namespace,就会在type上加上namespace。如果声明了{root:true}也使用原来的type。同时采用了Object.defineProperties()对getters和state做了延迟处理.
// 设置module上下文,绑定dispatch、commit、getters、state
function makeLocalContext (store, namespace, path) {
// 判断是否存在命名空间
const noNamespace = namespace === ''
const local = {
// 不存在命名空间就使用原来的
dispatch: noNamespace ? store.dispatch : (_type, _payload, _options) => {
// 统一对象格式
const args = unifyObjectStyle(_type, _payload, _options)
const { payload, options } = args
let { type } = args
if (!options || !options.root) {
// 类型上加上命名空间
type = namespace + type
if (__DEV__ && !store._actions[type]) {
console.error(`[vuex] unknown local action type: ${args.type}, global type: ${type}`)
return
}
}
// 触发action
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 (__DEV__ && !store._mutations[type]) {
console.error(`[vuex] unknown local mutation type: ${args.type}, global type: ${type}`)
return
}
}
store.commit(type, payload, options)
}
}
// getters and state object must be gotten lazily
// because they will be changed by vm update
Object.defineProperties(local, {
getters: {
get: noNamespace
? () => store.getters
: () => makeLocalGetters(store, namespace)
},
state: {
get: () => getNestedState(store.state, path)
}
})
return local
}
registerMutation()
function registerMutation (store, type, handler, local) {
// 判断是否存在该mutation类型,不存在就提供一个空数组
const entry = store._mutations[type] || (store._mutations[type] = [])
// 可重复注册mutation
entry.push(function wrappedMutationHandler (payload) {
handler.call(store, local.state, payload)
})
}
通过commit触发mutation
commit()
this.$store.commit(mutationType, payload)
commit (_type, _payload, _options) {
// check object-style commit
const {
type,
payload,
options
} = unifyObjectStyle(_type, _payload, _options)
const mutation = { type, payload }
// 获取type对应mutations
const entry = this._mutations[type]
if (!entry) {
if (__DEV__) {
console.error(`[vuex] unknown mutation type: ${type}`)
}
return
}
// 在_withCommit中执行mutation,mutation是修改state的唯一方法
this._withCommit(() => {
entry.forEach(function commitIterator (handler) {
handler(payload)
})
})
// 通知更新
this._subscribers
.slice() // shallow copy to prevent iterator invalidation if subscriber synchronously calls unsubscribe
.forEach(sub => sub(mutation, this.state))
if (
__DEV__ &&
options && options.silent
) {
console.warn(
`[vuex] mutation type: ${type}. Silent option has been removed. ` +
'Use the filter functionality in the vue-devtools'
)
}
}
registerActions()
function registerAction (store, type, handler, local) {
// 根据type获取actions
const entry = store._actions[type] || (store._actions[type] = [])
entry.push(function wrappedActionHandler (payload) {
// 参数较多,用于解构获取commit,state等
let res = handler.call(store, {
dispatch: local.dispatch,
commit: local.commit,
getters: local.getters,
state: local.state,
rootGetters: store.getters,
rootState: store.state
}, payload)
// 如果action的执行结果不是promise类型,将其包裹其promise,用于promise的链式调用
if (!isPromise(res)) {
res = Promise.resolve(res)
}
if (store._devtoolHook) {
// 使用devtool处理一次error
return res.catch(err => {
store._devtoolHook.emit('vuex:error', err)
throw err
})
} else {
return res
}
})
}
dispatch()
对参数统一处理后获取到action数组,数组长度大于一,用promise.all进行处理。
dispatch (_type, _payload) {
// 统一格式
const {
type,
payload
} = unifyObjectStyle(_type, _payload)
// 组成action
const action = { type, payload }
// 获取actions数组
const entry = this._actions[type]
// 不存在该action
if (!entry) {
if (__DEV__) {
console.error(`[vuex] unknown action type: ${type}`)
}
return
}
// 在执行action前
try {
this._actionSubscribers
.slice()
.filter(sub => sub.before)
.forEach(sub => sub.before(action, this.state))
} catch (e) {
if (__DEV__) {
console.warn(`[vuex] error in before action subscribers: `)
console.error(e)
}
}
const result = entry.length > 1
? Promise.all(entry.map(handler => handler(payload)))
: entry[0](payload)
return new Promise((resolve, reject) => {
result.then(res => {
// 在执行action后
try {
this._actionSubscribers
.filter(sub => sub.after)
.forEach(sub => sub.after(action, this.state))
} catch (e) {
if (__DEV__) {
console.warn(`[vuex] error in after action subscribers: `)
console.error(e)
}
}
resolve(res)
}, error => {
try {
this._actionSubscribers
.filter(sub => sub.error)
.forEach(sub => sub.error(action, this.state, error))
} catch (e) {
if (__DEV__) {
console.warn(`[vuex] error in error action subscribers: `)
console.error(e)
}
}
reject(error)
})
})
}
registerGetters()
function registerGetter (store, type, rawGetter, local) {
// 不能重复定义getters
if (store._wrappedGetters[type]) {
if (__DEV__) {
console.error(`[vuex] duplicate getter key: ${type}`)
}
return
}
// 包裹一层_wrappedGetters
store._wrappedGetters[type] = function wrappedGetter (store) {
return rawGetter(
local.state, // local state
local.getters, // local getters
store.state, // root state
store.getters // root getters
)
}
}
resetStoreVM()
重新设置一个新的vue实例,用于保存state和getter,使用computed来保存getters,同时给getters加上了一层代理,使得可以通过this.$store.getters.xx进行访问。通过判断是否存在oldVm,解除state引用,通过销毁旧的vue实例。
function resetStoreVM (store, state, hot) {
// 保存旧vm
const oldVm = store._vm
store.getters = {}
// reset local getters cache
store._makeLocalGettersCache = Object.create(null)
// 获取store中外层的getters
const wrappedGetters = store._wrappedGetters
const computed = {}
// 遍历getters,通过Object.defineProperty()给getters对象建立属性,就可以通过this.$store.getters.xx访问
forEachValue(wrappedGetters, (fn, key) => {
computed[key] = partial(fn, store)
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
store._vm = new Vue({
data: {
$$state: state
},
computed
})
Vue.config.silent = silent
// enable strict mode for new vm
if (store.strict) {
enableStrictMode(store)
}
// 如果存在oldVm,解除对state的引用,等dom更新后把旧的vue实例销毁
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())
}
}
enableStrictMode()
使用watch来观察state的变化,在mutation之后都会改变_committing,如果_committing没有改变说明没有通过mutation改变状态。
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 })
}
watch()
用于监听getter的变化
watch (getter, cb, options) {
if (__DEV__) {
// 判断getter类型,必须为function类型
assert(typeof getter === 'function', `store.watch only accepts a function.`)
}
// 调用vue中的watch方法
return this._watcherVM.$watch(() => getter(this.state, this.getters), cb, options)
}
replaceState()
修改state
replaceState (state) {
this._withCommit(() => {
this._vm._data.$$state = state
})
}
registerModule()
动态注册module
registerModule (path, rawModule, options = {}) {
if (typeof path === 'string') path = [path]
if (__DEV__) {
assert(Array.isArray(path), `module path must be a string or an Array.`)
assert(path.length > 0, 'cannot register the root module by using registerModule.')
}
this._modules.register(path, rawModule)
// 安装模块
installModule(this, this.state, path, this._modules.get(path), options.preserveState)
// reset store to update getters...
// 更新
resetStoreVM(this, this.state)
}
_withCommit()
当改变mutations时会将_committing设置成true,执行完后重置。
_withCommit (fn) {
const committing = this._committing
this._committing = true
fn()
this._committing = committing
}