vuex原理
每个组件(vue实例),在beforeCreate
的生命周期中混入同一个Store
实例作为属性$store
。同时$store
是响应式的。
Vuex 挂载
vue使用vuex就是通过Vue.use(Vuex)
,Vue.use
本质上就是执行参数的install
方法,如果参数本身就是函数,那么只执行这个函数。
install
let Vue;
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;
applyMixin(Vue);
}
install
函数会判断_Vue
是否与传入的Vue
参数相等,避免重复挂载。然后是执行applyMixin
主逻辑
- applyMixin
applyMixin
函数通过Vue.mixin
在beforeCreate
的生命周期钩子执行 vuexInit
方法。vuexInit
方法则是将store
注入每个vue实例。
export default function (Vue) {
const version = Number(Vue.version.split('.')[0])
if (version >= 2) {
// 正如前面所说的,store实例注入的生命周期是beforeCreate
Vue.mixin({ beforeCreate: vuexInit })
} else {
// override init and inject vuex init procedure
// for 1.x backwards compatibility.
// 兼容vue的1.x版本
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 // Vue传入的参数
// store injection
// 注入store
if (options.store) {
this.$store = typeof options.store === 'function'
? options.store()
: options.store
} else if (options.parent && options.parent.$store) {
this.$store = options.parent.$store
}
}
}
最终每个Vue
的实例对象,都回有$store
属性,且是同一个Store
实例。
Vuex.Store
传入的store
实例是通过Vuex.Store
类生成的,如官网示例代码:
const store = new Vuex.Store({
state: {
count: 0
},
mutations: {
increment (state) {
state.count++
}
}
})
下面通过vuex.Store
的源码逐步分析实例化store
过程中发生了什么。
构造函数
export class Store {
constructor (options = {}) {
// 这个构造函数比较长,这里省略,后文分开细述
}
}
if (!Vue && typeof window !== 'undefined' && window.Vue) {
install(window.Vue)
}
如果是 cdn script
方式引入vuex
插件,则自动安装vuex
插件,不需要用Vue.use(Vuex)
来安装。
// asset 函数实现
export function assert (condition, msg) {
if (!condition) throw new Error(`[vuex] ${msg}`)
}
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.`)
}
实现asset
函数的目的。尽早抛出异常,阻止程序运行,同时也方便后期拓展。
1.必须使用 Vue.use(Vuex) 创建 store 实例。
2.当前环境不支持Promise,报错:vuex 需要 Promise polyfill。
3.Store 函数必须使用 new 操作符调用。
const {
// 插件默认是空数组
plugins = [],
// 严格模式默认是false
strict = false
} = options
从用户定义的new Vuex.Store(options)
取出plugins
和strict
参数。
// store internal state
this._committing = false
// 用来存放处理后的用户自定义的actoins
this._actions = Object.create(null)
// 用来存放 actions 订阅
this._actionSubscribers = []
// 用来存放处理后的用户自定义的mutations
this._mutations = Object.create(null)
// 用来存放处理后的用户自定义的 getters
this._wrappedGetters = Object.create(null)
// 模块收集器,构造模块树形结构
this._modules = new ModuleCollection(options)
// 用于存储模块命名空间的关系
this._modulesNamespaceMap = Object.create(null)
// 订阅
this._subscribers = []
// 用于使用 $watch 观测 getters,提供给外部使用
this._watcherVM = new Vue()
// 用来存放生成的本地 getters 的缓存
this._makeLocalGettersCache = Object.create(null)
声明Store
实例对象一些内部变量。用于存放处理后用户自定义的actions、mutations、getters等变量。
// bind commit and dispatch to self
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)
}
为什么要这样绑定?
确保调用commit
和dispatch
函数的this
是sotre
实例,比如下面的例子,当使用ES2015的参数解构时,如果没有绑定上下文,在严格模式下,this
为undefined
actions: {
increment ({ commit }) {
commit('increment')
}
}
// 严格模式,默认是false
this.strict = strict
// 根模块的state
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)
installModule
函数,初始化根模块,并且递归遍历所有子模块,并且收集所有模块的gettter
放置在_wrappedGetters
中。
resetStoreVM
函数, 初始化 store._vm
响应式,并且注册 _wrappedGetters
作为 它的computed
的属性
// apply plugins
// 插件:把实例对象 store 传给插件函数,执行所有插件。
plugins.forEach(plugin => plugin(this))
// 初始化 vue-devtool 开发工具。这部分忽略
const useDevtools = options.devtools !== undefined ? options.devtools : Vue.config.devtools
if (useDevtools) {
devtoolPlugin(this)
}
整个构造函数的重点理解,其实也就是
this._modules = new ModuleCollection(options)
installModule(this, state, [], this._modules.root)
resetStoreVM(this, state)
installModule
function installModule (store, rootState, path, module, hot) {
// 是根模块
const isRoot = !path.length
// 命名空间 字符串
const namespace = store._modules.getNamespace(path)
if (module.namespaced) {
// 省略代码: 模块命名空间map对象中已经有了,开发环境报错提示重复
// module 赋值给 _modulesNamespaceMap[namespace]
store._modulesNamespaceMap[namespace] = module
}
// ... 后续代码 移出来 待读解释
}
设置 state
// 不是根模块且不是热重载
if (!isRoot && !hot) {
const parentState = getNestedState(rootState, path.slice(0, -1))
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('.')}"`
)
}
}
Vue.set(parentState, moduleName, module.state)
})
}
const local = module.context = makeLocalContext(store, namespace, path)
module.context 这个赋值主要是给 helpers 中 mapState、mapGetters、mapMutations、mapActions四个辅助函数使用的。
生成本地的dispatch、commit、getters和state。
主要作用就是抹平差异化,不需要用户再传模块参数
遍历注册 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.forEachChild((child, key) => {
installModule(store, rootState, path.concat(key), child, hot)
})
resetStoreVM 函数 (重点)
resetStoreVM(this, state)
初始化store vm,它负责响应式,(也注册_wrappedGetters作为计算属性)
function resetStoreVM (store, state, hot) {
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 = {}
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是就创建一个Vue实例,用来存储整个state树
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)
}
// 销毁
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())
}
}
store vm
本质上就是一个Vue实例,它用来存储整个state
树,利用vue
的能力来实现响应式--$$state
作为vue实例的data
属性,而getter
则作为vue实例的computer
属性。
ModuleCollection
类
// new实例执行 this.register方法,注册根模块(Vuex.Store options)
var ModuleCollection = function ModuleCollection (rawRootModule) {
// register root module (Vuex.Store options)
this.register([], rawRootModule, false);
};
// 根据path路径获取对应的module对象,从根模块出发
ModuleCollection.prototype.get = function get (path) {
return path.reduce(function (module, key) {
return module.getChild(key)
}, this.root)
};
ModuleCollection.prototype.getNamespace = function getNamespace (path) {
var module = this.root;
return path.reduce(function (namespace, key) {
module = module.getChild(key);
return namespace + (module.namespaced ? key + '/' : '')
}, '')
};
ModuleCollection.prototype.update = function update$1 (rawRootModule) {
update([], this.root, rawRootModule);
};
ModuleCollection.prototype.register = function register (path, rawModule, runtime) {
var this$1 = this;
// 若runtime不传,则默认为true,上文可知传的值为false
if ( runtime === void 0 ) runtime = true;
// 开发环境中,依次对传入的rawModule(Vue strore Option)的getter、mutation、action做类型校验(若存在)。
if ((process.env.NODE_ENV !== 'production')) {
assertRawModule(path, rawModule);
}
// 创建一个新模块,Module类, store'module的基础数据接口,本质上是树结构
var newModule = new Module(rawModule, runtime);
// path为[],则代表为根模块
if (path.length === 0) {
this.root = newModule;
} else {
// 获取带添加的的父模块节点,并添加新的模块
var parent = this.get(path.slice(0, -1));
parent.addChild(path[path.length - 1], newModule);
}
// register nested modules
// 继续递归注册所有的modules对象
if (rawModule.modules) {
forEachValue(rawModule.modules, function (rawChildModule, key) {
this$1.register(path.concat(key), rawChildModule, runtime);
});
}
};
ModuleCollection.prototype.unregister = function unregister (path) {
var parent = this.get(path.slice(0, -1));
var key = path[path.length - 1];
var child = parent.getChild(key);
if (!child) {
if ((process.env.NODE_ENV !== 'production')) {
console.warn(
"[vuex] trying to unregister module '" + key + "', which is " +
"not registered"
);
}
return
}
if (!child.runtime) {
return
}
parent.removeChild(key);
};
ModuleCollection.prototype.isRegistered = function isRegistered (path) {
var parent = this.get(path.slice(0, -1));
var key = path[path.length - 1];
if (parent) {
return parent.hasChild(key)
}
return false
};
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。