vuex@3.x实现分析

本文最后更新于:2022年5月31日 早上

初始化阶段

安装 Vuex

在组件beforeCreate的钩子中将 vuex 的Store实例挂载在实例this.$store上;

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) {
		// 如果是子组件中执行,也会指向最顶层的$store
		this.$store = options.parent.$store;
	}
}

实例化 Store

// 调用new Store()
export class Store {
	constructor(options = {}) {
		// options是vue实例化的options

		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(); /*用一个新的vue实例作为vuexWatcher*/

		// 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);
		};
		// 根store就是_module.root
		const state = this._modules.root.state;

		/* ⭐️安装模块 */
		installModule(this, state, [], this._modules.root);

		// 初始化 store vm, 负责提供相应式
		// (also registers _wrappedGetters as computed properties)
		resetStoreVM(this, state);

		// 应用plugin
		plugins.forEach((plugin) => plugin(this));

		if (Vue.config.devtools) {
			// devtool
			devtoolPlugin(this);
		}
	}
}

初始化模块

从根module开始,按照层级关系递归实例化所有的子module为一整个树形结构的对象

export default class ModuleCollection {
	constructor(rawRootModule) {
		// 为根store模块开始执行register方法
		this.register([], rawRootModule, false);
	}

	// 根据path去查找模块
	get(path) {
		return path.reduce((module, key) => {
			return module.getChild(key);
			/**
			 * getChild (key) {
			 *   return this._children[key]
			 * }
			 */
		}, this.root);
	}

	register(path, rawModule, runtime = true) {
		/// ...
		const newModule = new Module(rawModule, runtime);
		// 生成单个的module实例

		if (path.length === 0) {
			// path长度是0说明是根module
			this.root = newModule;
		} else {
			// 否则添加为上级的子模块
			const parent = this.get(path.slice(0, -1));
			parent.addChild(path[path.length - 1], newModule);
			/**
			 * addChild (key, module) {
			 *   this._children[key] = module
			 * }
			 */
		}

		// 为当前模块中的所有子模块递归调用register
		if (rawModule.modules) {
			forEachValue(rawModule.modules, (rawChildModule, key) => {
				this.register(path.concat(key), rawChildModule, runtime);
			});
		}
	}
}

安装模块

function installModule(store, rootState, path, module, hot) {
	// 判断是否根模块
	const isRoot = !path.length;
	// 判断有没有命名空间
	const namespace = store._modules.getNamespace(path);
	// return namespace + (module.namespaced ? key + '/' : '')

	// 注册到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));
	// 创建一个local上下文
	// 对于有namespace的dispatch和commit会拼接上namespace
	// 对于有namespace的getter有特殊处理,不是直接返回而是用Object.defineProperty设置了getter

	/** 
   * function makeLocalContext (store, namespace, path) {
        const noNamespace = namespace === ''
        const local = {
          dispatch: noNamespace ? store.dispatch : (_type, _payload, _options) => {...}),
          commit: noNamespace ? store.commit : (_type, _payload, _options) => {...})
        }
        
        Object.defineProperties(local, {
          getters: {
            get: noNamespace
              ? () => store.getters
              : () => makeLocalGetters(store, namespace)
          },
          state: {
            get: () => getNestedState(store.state, path)
          }
        })
        return local
        }
  */

 	// 注册	 各种方法
	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);
	});
}

实例化store.vm

  • 建立getterstate之间的联系
  • 使用一个vue实例来存储state,访问了 store._vm[key],也就是 computed[key]
  • computed的特性可以缓存并且state变化的时候才会重新计算
  • store.state 发生变化的时候,下一次再访问 store.getters 的时候会重新计算。
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
    })
  })

  // 使用一个vue实例来存储state,访问state就是访问computed[key],
	// 实现state改变的时候getter才改变,并且被缓存
  store._vm = new Vue({
    data: {
      $$state: state
    },
    computed
  })


  // enable strict mode for new vm
  if (store.strict) {
    enableStrictMode(store)
  }
	// 严格模式会添加一个watch监测state的改变是否来自非vuex接口

	// function enableStrictMode (store) {
	// 	store._vm.$watch(function () { return this._data.$$state }, () => {
	// 		if (process.env.NODE_ENV !== 'production') {
	// 			assert(store._committing, `Do not mutate vuex store state outside mutation handlers.`)
	// 		}
	// 	}, { deep: true, sync: true })
	// }

  if (oldVm) {
    Vue.nextTick(() => oldVm.$destroy())
  }
}

归纳

  1. beforeCreate钩子中混入vuexInit()方法
  2. 通过递归判断是否有父组件,绑定this.$store指向到实例化的Store
  3. 从根module按照父子层级计算出完整的module
  4. 从根module开始梳理有namespace的模块,会在他路径前拼接上namespace,并保存下一个_modulesNamespaceMap
  5. 遍历模块中的所有子 modules,递归执行 installModule 方法,计算各自state``getter``commit``dispatch的访问路径
  6. 按照各个API的入口,将自定义的回调函数加入入口数组
  7. getter的特性会使用一个vue实例和computed属性实现,只有getter依赖的state变化后getter的结果也会变化

vuex@3.x实现分析
http://yoursite.com/2022/03/14/[源码]vue2-vuex/
作者
tatekii
发布于
2022年3月14日
许可协议