🔥码云GVP开源项目 12k star Uniapp+ElementUI 功能强大 支持多语言、二开方便! 广告
>PS:个人笔记,仅供参考,需要深入了解请阅读参考资料。 [TOC] # 参考资料 [https://ustbhuangyi.github.io/vue-analysis/prepare/directory.html#sfc](https://ustbhuangyi.github.io/vue-analysis/prepare/directory.html#sfc) # Vuex 初始化 ## 安装 和 Vue-Router 一样,Vuex 也同样存在一个静态的`install`方法,它的定义在`src/store.js`中: ```js 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 applyMixin(Vue) } ``` `install`的逻辑很简单,把传入的`_Vue`赋值给`Vue`并执行了`applyMixin(Vue)`方法,它的定义在`src/mixin.js`中: ```js 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 // store injection 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 } } } ``` `applyMixin`全局混入了一个`beforeCreate`钩子函数,它的实现非常简单,就是把`options.store`保存在所有组件的`this.$store`中,这个`options.store`就是`Store`对象的实例。 ## Store 实例化 我们在`import Vuex`之后,会实例化其中的`Store`对象,返回`store`实例并传入`new Vue`的`options`中,也就是我们刚才提到的`options.store`.就像下面这样: ```js export default new Vuex.Store({ actions, getters, state, mutations, modules // ... }) ``` `Store`对象的构造函数接收一个对象参数,它包含`actions`、`getters`、`state`、`mutations`、`modules`等 Vuex 的核心概念,它的定义在`src/store.js`中: ```js export class 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 = [], strict = false } = options // store internal state this._committing = false this._actions = Object.create(null) this._actionSubscribers = [] this._mutations = Object.create(null) this._wrappedGetters = Object.create(null) this._modules = new ModuleCollection(options) this._modulesNamespaceMap = Object.create(null) this._subscribers = [] this._watcherVM = new Vue() // 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) } // strict mode this.strict = strict 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) // apply plugins plugins.forEach(plugin => plugin(this)) if (Vue.config.devtools) { devtoolPlugin(this) } } } ``` 这里有三个关键点: - 初始化模块 ```js this._modules = new ModuleCollection(options) ``` - 安装模块 ```js // init root module. // this also recursively registers all sub-modules // and collects all module getters inside this._wrappedGetters installModule(this, state, [], this._modules.root) ``` - 初始化`store_.vm` ```js // initialize the store vm, which is responsible for the reactivity // (also registers _wrappedGetters as computed properties) resetStoreVM(this, state) ``` ### 初始化模块 在分析模块初始化之前,我们先来了解一下模块对于 Vuex 的意义:由于使用单一状态树,应用的所有状态会集中到一个比较大的对象,当应用变得非常复杂时,`store`对象就有可能变得相当臃肿。为了解决以上问题,Vuex 允许我们将`store`分割成模块(module)。每个模块拥有自己的`state`、`mutation`、`action`、`getter`,甚至是嵌套子模块——从上至下进行同样方式的分割: ```js const moduleA = { state: { ... }, mutations: { ... }, actions: { ... }, getters: { ... } } const moduleB = { state: { ... }, mutations: { ... }, actions: { ... }, getters: { ... }, } const store = new Vuex.Store({ modules: { a: moduleA, b: moduleB } }) store.state.a // -> moduleA 的状态 store.state.b // -> moduleB 的状态 ``` `Module`是用来描述单个模块的类,它的定义在`src/module/module.js`中: ```js export default class Module { constructor (rawModule, runtime) { this.runtime = runtime // Store some children item this._children = Object.create(null) // Store the origin module object which passed by programmer this._rawModule = rawModule const rawState = rawModule.state // Store the origin module's state this.state = (typeof rawState === 'function' ? rawState() : rawState) || {} } get namespaced () { return !!this._rawModule.namespaced } addChild (key, module) { this._children[key] = module } removeChild (key) { delete this._children[key] } getChild (key) { return this._children[key] } update (rawModule) { this._rawModule.namespaced = rawModule.namespaced if (rawModule.actions) { this._rawModule.actions = rawModule.actions } if (rawModule.mutations) { this._rawModule.mutations = rawModule.mutations } if (rawModule.getters) { this._rawModule.getters = rawModule.getters } } forEachChild (fn) { forEachValue(this._children, fn) } forEachGetter (fn) { if (this._rawModule.getters) { forEachValue(this._rawModule.getters, fn) } } forEachAction (fn) { if (this._rawModule.actions) { forEachValue(this._rawModule.actions, fn) } } forEachMutation (fn) { if (this._rawModule.mutations) { forEachValue(this._rawModule.mutations, fn) } } } ``` 对于每个模块而言,`this._rawModule`表示模块的配置,`this._children`表示它的所有子模块,`this.state`表示这个模块定义的`state`。 ### 安装模块 初始化模块后,执行安装模块的相关逻辑,它的目标就是对模块中的`state`、`getters`、`mutations`、`actions`做初始化工作 ```js function installModule (store, rootState, path, module, hot) { const isRoot = !path.length const namespace = store._modules.getNamespace(path) // register in namespace map if (module.namespaced) { 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) }) } const local = module.context = makeLocalContext(store, namespace, path) module.forEachMutation((mutation, key) => { const namespacedType = namespace + key registerMutation(store, namespacedType, mutation, local) }) module.forEachAction((action, key) => { const type = action.root ? key : namespace + key const handler = action.handler || action registerAction(store, type, handler, local) }) 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) }) } ``` 这里涉及到了命名空间的概念,默认情况下,模块内部的`action`、`mutation`和`getter`是注册在全局命名空间的——这样使得多个模块能够对同一`mutation`或`action`作出响应。如果我们希望模块具有更高的封装度和复用性,可以通过添加`namespaced: true`的方式使其成为带命名空间的模块。当模块被注册后,它的所有`getter`、`action`及`mutation`都会自动根据模块注册的路径调整命名。 ### 初始化`store._vm` `Store`实例化的最后一步,就是执行初始化`store._vm`的逻辑,它的入口代码是: ```js resetStoreVM(this, state) ``` ```js function resetStoreVM (store, state, hot) { const oldVm = store._vm // bind store public getters store.getters = {} const wrappedGetters = store._wrappedGetters const computed = {} forEachValue(wrappedGetters, (fn, key) => { // use computed to leverage its lazy-caching mechanism computed[key] = () => 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 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()) } } ``` `resetStoreVM`的作用实际上是想建立`getters`和`state`的联系,因为从设计上`getters`的获取就依赖了`state`,并且希望它的依赖能被缓存起来,且只有当它的依赖值发生了改变才会被重新计算。因此这里利用了 Vue 中用`computed`计算属性来实现。 `resetStoreVM`首先遍历了`_wrappedGetters`获得每个`getter`的函数`fn`和`key`,然后定义了`computed[key] = () => fn(store)`。我们之前提到过`_wrappedGetters`的初始化过程,这里`fn(store)`相当于执行如下方法: ```js store._wrappedGetters[type] = function wrappedGetter (store) { return rawGetter( local.state, // local state local.getters, // local getters store.state, // root state store.getters // root getters ) } ``` 返回的就是`rawGetter`的执行函数,`rawGetter`就是用户定义的`getter`函数,它的前 2 个参数是`local state`和`local getters`,后 2 个参数是`root state`和`root getters`。 接着实例化一个 Vue 实例`store._vm`,并把`computed`传入: ```js store._vm = new Vue({ data: { $$state: state }, computed }) ``` 我们发现`data`选项里定义了`$$state`属性,而我们访问`store.state`的时候,实际上会访问`Store`类上定义的`state`的`get`方法: ```js get state () { return this._vm._data.$$state } ``` 它实际上就访问了`store._vm._data.$$state`。那么`getters`和`state`是如何建立依赖逻辑的呢,我们再看这段代码逻辑: ```js forEachValue(wrappedGetters, (fn, key) => { // use computed to leverage its lazy-caching mechanism computed[key] = () => fn(store) Object.defineProperty(store.getters, key, { get: () => store._vm[key], enumerable: true // for local getters }) }) ``` ### 总结 我们要把`store`想象成一个数据仓库,为了更方便的管理仓库,我们把一个大的`store`拆成一些`modules`,整个`modules`是一个树型结构。每个`module`又分别定义了`state`,`getters`,`mutations`、`actions`,我们也通过递归遍历模块的方式都完成了它们的初始化。为了`module`具有更高的封装度和复用性,还定义了`namespace`的概念。最后我们还定义了一个内部的`Vue`实例,用来建立`state`到`getters`的联系,并且可以在严格模式下监测`state`的变化是不是来自外部,确保改变`state`的唯一途径就是显式地提交`mutation`。