首发于不止前端

Vuex源码解析一

Anli Li:Vuex源码解析一zhuanlan.zhihu.com图标Anli Li:Vuex源码解析二zhuanlan.zhihu.com图标Anli Li:Vuex源码解析三zhuanlan.zhihu.com图标Anli Li:Vuex源码解析四及对比Reduxzhuanlan.zhihu.com图标

先说一下整体感觉,Vuex的源码没有Redux的源码来的好读,不过使用起来还是要比Redux简单,没有复杂头晕的中间件。Vuex属于那种把复杂留给自己,用简单照亮别人的库(Redux如果没有react-redux配合会很难受)。

以下是我看完官方文档之后,列出的问题,带着问题来浏览源码

准备

先把代码扒拉下来,不管三七二十一,先读一遍package.json

然后执行一下npm install,安装完成后执行一下npm run dev,会启动一个本地开发环境,把example目录下的例子暴露出来,这一步最好做一下,因为启动后,官方示例是直接关联到Vuex源码的,方便debug理解内部原理。

源码结构超级简单

 │__helpers.js
 │__index.esm.js
 │__index.js
 │__mixin.js
 │__store.js
 │__util.js
 │
 ├─module
 │____module-collection.js
 │____module.js
 │
 ├─plugins
 │____devtool.js
 │____logger.js

最终在index.js里暴露出来的是Vuex这个对象

 export default {
   Store,
   install,
   version: '__VERSION__',
   mapState,
   mapMutations,
   mapGetters,
   mapActions,
   createNamespacedHelpers
 }

初始化

先看关于Vuex初始化留下的疑问

  1. 作为插件,install安装过程是怎样的
  2. 全局引入的情况下,如何保证跟install里执行一样的准备
  3. $store是如何放入每一个Vue实例的,并且保证$store指向同一个对象

最常规的用法是

 import Vue from 'vue'
 import Vuex from 'vuex'
 
 Vue.use(Vuex)

但是官方文档里也说了

当使用全局 script 标签引用 Vuex 时,不需要以上安装过程

既然Vuex是一个插件,那么它必然要有install方法,这在刚才的index.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);
 }

注意这里取名为Vue的变量,我对此一开始还感到了疑惑。它其实是这个文件里用let Vue定义的变量,当然作用其实也是为了接受真正的Vue。我感觉是它取名为_Vue要好一些,然后在install方法形参取名为Vue

之所以专门用一个变量来接受Vue为的就是防止重复安装并且不需要引入Vue,避免打包的时候打入。接下来看一下applyMixin的核心,就一句话

 Vue.mixin({ beforeCreate: vuexInit })

兜兜转转,从plugin又来到了mixin,至此第一个问题得到了回答,现在来看一下vuexInit方法

 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
     }
 }

这一段就能回答第三个问题了,对于第三个问题,我的第一反应是VuexVueprototype上放上了$store,这样每一个Vue实例生成的时候,都自动有了$store,对于vue-router还真是这么做的。但是在Vuex里,用了另外一种方式——先在根部Vue组件里放入$store,然后在每一个Vue实例创建出来的时候,从父组件上把$store拿下来,由于Vue组件的生成是自上而下有序进行的,所以就能保证每一个Vue实例都能获得根部的$store实例,并且是同一个。

接下来看第二个问题,全局引入的话,没有Vue.use(Vuex)安装插件的过程,但是无论如何Vuex.Store的实例还是要创建出来的,于是install方法就出现在了它的constructor

 if (!Vue && typeof window !== "undefined" && window.Vue) {
     install(window.Vue);
 }

编辑于 03-09

文章被以下专栏收录