Vuex概述
Vuex是一个专为 Vue.js 应用程序开发的状态管理模式,它采用集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化。在开发当中,运用Vuex统一管理我们的资源,使得开发更明确,适合用在中大型项目。
tips:本文不对Vuex的使用做叙述,只对Vuex源码进行解析,以下跳转Vuex官方中文文档:https://vuex.vuejs.org/zh/
Vuex状态图与源码结构
Vuex-install
我们知道,Vue在调用Vue.use()安装插件时,即会调用安装插件的方法。
而在Vuex中,则是调用如下方法:
// 暴露给外部的插件install方法,供Vue.use调用安装插件
export function install(_Vue) {
if (Vue && _Vue === Vue) { // 避免重复安装
if (process.env.NODE_ENV !== 'production') {
console.error(
'[vuex] already installed. Vue.use(Vuex) should be called only once.'
)
}
return
}
// 将外界全局vue实例绑定到内部使用的Vue
Vue = _Vue
// 根据Vue传入的options ,给Vue实例mixin钩子函数 beforeCreate,实例化 store()
applyMixin(Vue)
其中,applyMixin(Vue)会根据Vue的版本,将vuexinit方法mixin到Vue的beforeCreate钩子函数列表中,在Vue实例化的时候调用,从而达到实例化我们的store的目的。而且,在vuexinit方法中,会判断当前vue组件是不是根组件,是,则直接赋值$store,不是,则通过options中的parent获取父组件的store引用,这样每个组件都可以获取到了同一份内存地址的Store实例,于是我们可以在每一个组件中访问全局的Store实例了。
/**
* vuexInit会尝试从options中获取store,如果当前组件是根组件(Root节点),则options中会存在store,直接获取赋值给$store即可
* 如果当前组件非根组件,则通过options中的parent获取父组件的$store引用
* 所有的组件都获取到了同一份内存地址的Store实例,于是我们可以在每一个组件中通过this.$store访问全局的Store实例了
*/
export default function (Vue) {
const version = Number(Vue.version.split('.')[0])
if (version >= 2) {
Vue.mixin({ beforeCreate: vuexInit }) // 初始化时,store()实例化Store
} 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.
*/
// Vuex 的钩子函数,会存入每一个Vue实例的钩子列表
function vuexInit() {
const options = this.$options
// new Vue 时有无添加store,有,则store()实例化
if (options.store) {
this.$store = typeof options.store === 'function'
? options.store()
: options.store
} else if (options.parent && options.parent.$store) {
// 子组件直接从父组件中获取 $store ,这样就保证了所有组件都公用了全局的一份store
this.$store = options.parent.$store
}
}
}
看完vuex-install,我们再看Store内部定义了一些什么
Vuex class Store
首先,我们看一看构造函数contructor()
构造函数中,初始化了我们要用到的内部变量,并且通过installModule初始化了module,通过resetStoreVM新建一个Vue对象,使用Vue内部的响应式实现注册state以及computed
/**
* Store的构造类除了初始化一些内部变量,主要还执行了:
* installModule:初始化module
* resetStoreVM:通过VM使得store响应式
*/
constructor(options = {}) {
// Auto install if it is not done yet and `window` has `Vue`.
// To allow users to avoid auto-installation in some cases,
// this code should be placed here. See #731
if (!Vue && typeof window !== 'undefined' && window.Vue) {
install(window.Vue)
}
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 this browser.`)
assert(this instanceof Store, `store must be called with the new operator.`)
}
const {
// 插件数组
plugins = [],
/*使 Vuex store 进入严格模式,在严格模式下,任何 mutation 处理函数以外修改 Vuex state 都会抛出错误。*/
strict = false
} = options
// store internal state
this._committing = false // 用来判断严格模式下是否是用mutation修改state的
this._actions = Object.create(null) // 存放action
this._actionSubscribers = []
this._mutations = Object.create(null) // 存放mutation
this._wrappedGetters = Object.create(null) // 存放getters
this._modules = new ModuleCollection(options) // module收集器
this._modulesNamespaceMap = Object.create(null) // 根据namespace存放module
this._subscribers = [] // 存放订阅者
this._watcherVM = new Vue() // 用以实现Watch的Vue实例
this._makeLocalGettersCache = Object.create(null)
// 将dispatch和commit调用的this绑定为store对象本身,否则在组件内部this.dispatch时this会指向组件的vm
const store = this
const { dispatch, commit } = this
this.dispatch = function boundDispatch(type, payload) { // 向外部暴露的dispatch方法
return dispatch.call(store, type, payload) // 绑定store即this 到type指定的函数上,确保能够调用到this的其他参数
}
this.commit = function boundCommit(type, payload, options) { // 向外部暴露的commit方法
return commit.call(store, type, payload, options) // 绑定store即this 到type指定的函数上,确保能够调用到this的其他参数
}
// strict mode
this.strict = strict // 严格模式
const state = this._modules.root.state
// 初始化根module,这也同时递归注册了所有子module,收集所有module的getter到到_wrappedGetters中去,收集设置action,mutation,
installModule(this, state, [], this._modules.root) // this._modules.root代表根module才独有保存的Module对象
// 通过vm重设store,新建Vue对象使用Vue内部的响应式实现注册state以及computed
resetStoreVM(this, state)
// 应用 plugins
plugins.forEach(plugin => plugin(this))
// devtool插件
const useDevtools = options.devtools !== undefined ? options.devtools : Vue.config.devtools
if (useDevtools) {
devtoolPlugin(this)
}
}
然后我们来看installModule,其主要作用就是为module加上namespace命名空间(如果有),注册mutation,action,getter,同时递归安装所有子module
// 初始化module
function installModule(store, rootState, path, module, hot) {
const isRoot = !path.length // 判断是否是module的根节点
const namespace = store._modules.getNamespace(path) // 获取module的namespace
// 如果有namespace,则在_modulesNamespaceMap中注册
if (module.namespaced) {
if (store._modulesNamespaceMap[namespace] && process.env.NODE_ENV !== 'production') {
console.error(`[vuex] duplicate namespace ${namespace} for the namespaced module ${path.join('/')}`)
}
store._modulesNamespaceMap[namespace] = module // 注册
}
// set state
if (!isRoot && !hot) {
const parentState = getNestedState(rootState, path.slice(0, -1)) // 获取父级的state
// module的name
const moduleName = path[path.length - 1]
store._withCommit(() => {
if (process.env.NODE_ENV !== 'production') {
if (moduleName in parentState) {
console.warn(
`[vuex] state field "${moduleName}" was overridden by a module with the same name at "${path.join('.')}"`
)
}
}
// 将子module设成响应式的
Vue.set(parentState, moduleName, module.state)
})
}
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)
})
}
然后我们看到register注册action和mutation时,巧妙地用call()将作用域绑定到store实例上,并且暴露state等我们需要的内部参数。
// 遍历注册mutation
function registerMutation(store, type, handler, local) {
const entry = store._mutations[type] || (store._mutations[type] = [])
entry.push(function wrappedMutationHandler(payload) {
handler.call(store, local.state, payload)
})
}
// 遍历注册action
function registerAction(store, type, handler, local) {
// 取出type对应的action
const entry = store._actions[type] || (store._actions[type] = [])
// 对action进行一层封装,call()绑定作用域与state,commit等方法,使得我们在dispatch第一个参数中可以获取
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)
// action执行结果判断是否是Promise
if (!isPromise(res)) {
// 不是Promise对象的时候转换成Promise对象
res = Promise.resolve(res)
}
if (store._devtoolHook) {
// 存在devtool插件时触发vuex的error给devtool
return res.catch(err => {
store._devtoolHook.emit('vuex:error', err)
throw err
})
} else {
return res
}
})
}
// 遍历注册getter
function registerGetter(store, type, rawGetter, local) {
if (store._wrappedGetters[type]) {
if (process.env.NODE_ENV !== 'production') {
console.error(`[vuex] duplicate getter key: ${type}`)
}
return
}
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和computed
function resetStoreVM(store, state, hot) {
// 存放之前的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 = {}
// 通过Object.defineProperty为每一个getter方法设置get方法,依赖vm响应式
forEachValue(wrappedGetters, (fn, key) => {
// 遍历wrappedGetters,绑定get方法,这样我们就可以在组件通过this.$store.getters的方式访问
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
const silent = Vue.config.silent
// Vue.config.silent暂时设置为true的目的是在new一个Vue实例的过程中不会报出一切警告
Vue.config.silent = true
// 前面设置完get后,这里new一个Vue对象,运用Vue内部的响应式实现注册state和computed
store._vm = new Vue({
data: {
$$state: state
},
computed
})
Vue.config.silent = silent
// enable strict mode for new vm
// 使用严格模式,保证修改store只能通过mutation
if (store.strict) {
enableStrictMode(store)
}
if (oldVm) {
// 解除旧vm的state引用,以及销毁旧的Vue对象
if (hot) {
// dispatch changes in all subscribed watchers
// to force getter re-evaluation for hot reloading.
store._withCommit(() => {
oldVm._data.$$state = null // 解除旧的state引用
})
}
Vue.nextTick(() => oldVm.$destroy()) // 销毁旧的Vue对象
}
}
同时,我们都知道Vuex支持严格模式,即只能通过mutation修改vuex状态,他的原理很简单,是通过store实例的vm实例观察state,并判断_committing,而在commit调用方法前,修改_committing的值以达到监视效果,两部分的源码如下:
// Vuex执行严格模式下,$watch观察state,保证所有state的操作必须通过mutation实现
function enableStrictMode(store) {
store._vm.$watch(function () { return this._data.$$state }, () => {
if (process.env.NODE_ENV !== 'production') {
// 检查store中的_committing的值,如果是false代表不是通过mutations的方法修改的
assert(store._committing, `do not mutate vuex store state outside mutation handlers.`)
}
}, { deep: true, sync: true }) // 深度监视
}
// 在调用fn()前,修改this._committing,使得严格模式下内部监听state起作用 --function enableStrictMode
_withCommit(fn) { // 调用commit里对应的 fn()
const committing = this._committing
this._committing = true
fn()
this._committing = committing
}
接着我们看到Vuex暴露给外部的dispatch和commit方法,供外部vue实例调用mutation和action:
// 调用mutation的commit方法
commit(_type, _payload, _options) {
// 校验参数
const {
type,
payload,
options
} = unifyObjectStyle(_type, _payload, _options) // 判定是否为对象,返回原数据
const mutation = { type, payload }
// 取出type对应的mutation的方法
const entry = this._mutations[type] // 取出对应的函数
if (!entry) {
if (process.env.NODE_ENV !== 'production') {
console.error(`[vuex] unknown mutation type: ${type}`)
}
return
}
// 执行取出的方法
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 (
process.env.NODE_ENV !== 'production' &&
options && options.silent
) {
console.warn(
`[vuex] mutation type: ${type}. Silent option has been removed. ` +
'Use the filter functionality in the vue-devtools'
)
}
}
// 调用action的dispatch方法
dispatch(_type, _payload) {
// 校验参数
const {
type,
payload
} = unifyObjectStyle(_type, _payload) // 判定是否为对象,返回原数据
const action = { type, payload }
const entry = this._actions[type] // 取出对应的函数
if (!entry) {
if (process.env.NODE_ENV !== 'production') {
console.error(`[vuex] unknown action type: ${type}`)
}
return
}
try {
this._actionSubscribers
.slice() // shallow copy to prevent iterator invalidation if subscriber synchronously calls unsubscribe
.filter(sub => sub.before)
.forEach(sub => sub.before(action, this.state))
} catch (e) {
if (process.env.NODE_ENV !== 'production') {
console.warn(`[vuex] error in before action subscribers: `)
console.error(e)
}
}
// 是数组则通过Promise.all()包装形成一个新的Promise,只有一个则直接返回
const result = entry.length > 1
? Promise.all(entry.map(handler => handler(payload))) // 调用
: entry[0](payload)
return result.then(res => {
try {
this._actionSubscribers
.filter(sub => sub.after)
.forEach(sub => sub.after(action, this.state))
} catch (e) {
if (process.env.NODE_ENV !== 'production') {
console.warn(`[vuex] error in after action subscribers: `)
console.error(e)
}
}
return res
})
}
除了满足基本使用的dispatch和action,向外暴露的还有:subscribe,watch, registerModule,unregisterModule 等,解析如下
/**
* Store给外部提供了一个subscribe方法,用以注册一个订阅函数,会push到Store实例的_subscribers中,同时返回一个从_subscribers中注销该订阅者的方法。
*/
// 注册一个订阅函数,返回取消订阅的函数
subscribe(fn) {
return genericSubscribe(fn, this._subscribers)
}
subscribeAction(fn) {
const subs = typeof fn === 'function' ? { before: fn } : fn
return genericSubscribe(subs, this._actionSubscribers)
}
// 观察一个getter方法
watch(getter, cb, options) {
if (process.env.NODE_ENV !== 'production') {
assert(typeof getter === 'function', `store.watch only accepts a function.`)
}
// _watcherVM是一个Vue实例,直接采用Vue内部的watch特性提供观察数据getter变动的方法
return this._watcherVM.$watch(() => getter(this.state, this.getters), cb, options)
}
replaceState(state) {
this._withCommit(() => {
this._vm._data.$$state = state
})
}
// registerModule用以注册一个动态模块,也就是在store创建以后再注册模块的时候用该接口,内部实现实际上也只有installModule与resetStoreVM两个步骤
// 注册一个动态module,当业务进行异步加载的时候,可以通过此接口进行注册动态module
registerModule(path, rawModule, options = {}) {
// 将path字符串转化成Array
if (typeof path === 'string') path = [path]
if (process.env.NODE_ENV !== 'production') {
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)
// 初始化module
installModule(this, this.state, path, this._modules.get(path), options.preserveState)
// 通过vm重设store,新建Vue对象使用Vue内部的响应式实现注册state以及computed
resetStoreVM(this, this.state)
}
// 与registerModule对应的方法unregisterModule,动态注销模块。实现方法是先从state中删除模块,然后用resetStore来重制store
// 注销一个动态module
unregisterModule(path) {
// 将path字符串转化成Array
if (typeof path === 'string') path = [path]
if (process.env.NODE_ENV !== 'production') {
assert(Array.isArray(path), `module path must be a string or an Array.`)
}
// 注销
this._modules.unregister(path)
this._withCommit(() => {
// 获取父级的state
const parentState = getNestedState(this.state, path.slice(0, -1))
// 从父级中删除模块
Vue.delete(parentState, path[path.length - 1])
})
// 重制store
resetStore(this)
}
结尾
可以看到,Vuex的实现原理很多地方用到了Vue的响应式原理,甚至直接在内部注册了Vue实例,其中call()的引用绑定作用域也是用得很妙,但在我觉得,运用箭头函数进行组件调用时的作用域绑定也可以实现功能。
总结不易,希望能帮到大家哦!!!
编者github地址:传送.
也欢迎和我一起讨论学习:
微信号:carfiedfeifei
qq:1073490398