Vuex 源码简析

153 阅读8分钟

版本,v3.6.0。主要分析 new Vuex.store(options) 内部机制。

install

先看下 Vuex 的导出:

const index = {
  Store,
  install,
  version: '3.6.0',
  mapState,
  mapMutations,
  mapGetters,
  mapActions,
  createNameSpacedHelpers,
  createLogger
}

export default index
export { Store, createLogger, createNamespacedHelpers, install, mapActions, mapGetters, mapMutations, mapState }

默认导出一个对象(即我们在日常开发中引入的 Vuex),对象的每个属性又分别具名导出。本篇只说 installStore

我们知道 Vue 的插件机制就在于插件的 install 方法,让 Vuex.use(Vue) 起作用的就是 install

let Vue

function install(_Vue) {
  if(Vue && _Vue === Vue) {
    // 已安装过
    return
  }
  
  Vue = _Vue
  applyMixin(Vue)
}

function applyMixin(Vue) {
  const vserion = Number(Vue.verion.split('.')[0])
  
  if(version >= 2) {
    // 全局 mixin 一个 beforeCreate hook
    Vue.mixin({ beforeCreate: vuexInit })
  } else {
    // 兼容 v2 以前的版本
  }
}

function vuexInit() {
  const options = this.$options
  
  // 为根组件实例添加 $store 属性
  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
  }
}

Vuex 插件的作用就是为每个 vm 实例添加 $store 属性,如果稍有留心的话,会发现其为每个组件添加同一个属性的方式在 Vue、Vue-router 中都是这样:根组件则直接赋值,非根组件则赋值为其父组件的对应属性值,这样下来从根到叶子,所有的实例都不会遗漏。

基本概念

在正式探索 new Store 之前,先介绍源码中的几个基本概念:

  • rawModule:具有 namespacedstategettersmutaionsactionsmodules 的对象,即可称为 rawModule

  • module:由构造函数 new Module(rawModule, runtime) 生成,拥有以下几个属性:

    • runtime:布尔类型,为 truefalse 的本质区别在于后者不能被 unregister
    • _children:保存着子 module 的对象,属性名为
    • _rawModule:指向传入构造器的 rawModule
    • staterawState.state 或者 rawState.state() 的返回值
    • namespaced:访问器属性,值为 !!rawModule.namespaced。一个 module 是否是 namespaced 的与否的区别在于这个属性会影响这个 modulenamespace 值(起这个值的方法为 ModuleColletion.prototype.getNamespace),这个值会对这个 modulegettersmutaionsactions 以及其 modules 的注册都有影响。一个 modulestate 则不受之影响,或者应该说,对于 state 而言,namespaced 是默认为 true 且无法修改的。

    以及一些对其 _chidlren 增删改查、遍历的方法和对 _rawModulegettersmutaionsactions 属性遍历的方法

  • path:每个 module 的唯一标识,值为一个数组,比如根 modulepath[],根 module 的子 moduleApath['moduleA']moduleA 的子 module1path['moduleA', 'module1'],依次类推。

  • moduleCollection:以 module 为节点组成 module 树,树的根为由以 storeOptionsrootRawModule 生成的 module,根 module 存储在 moduleCollectionroot 属性。除此之外,moduleCollection 有通过 modulepath 获取对应 module 和其 namespace 的方法以及注册、注销、更新 module 的方法。

明晰基本概念之后,new Store 就不难理解。

new Store

class Store {
  constructor(options = {}) {
    // 这个 options 也是 rawRootModule,只不过多了 plugins、strict、devtools 属性
    // strict 默认为 false,为 true 时会检验 state 树中每个属性的变化是否是 commit 一个 mutaion 导致,很消耗性能
    const { plugins = [], strict = false} = options
    
    // 上面说的 strict 为 true 时就是依靠 _commiting 属性来检验,commit 一个 mutaion 时,_committing 一定是 true 的,所以当 state 树中属性的 setter 被触发时,查询 _committing 值是否是 true 即可检验
    this._committing = false
    
    // 所有的 module 的 getters 均会被以其 namespace 为 key 注册到此,对应的 value 为对 getters 包装后的函数
    this._wrappedGetters = Object.create(null)
    // 用于缓存 namespaced 的 module 下的 getters,不必访问时每次都求值
    this._makeLocalGettersCache = Object.create(null)
    
    // 所有 module 的 mutaion 都会被以其 namespace 为 key 注册到此,对应的 value 为所有相同 namespace 的 mutaion 对应的 handler
    this._mutaions = Object.create(null)
    // 订阅 mutaion 的回调
    this._subscibers = []
    
    // 同 _mutaions 和 _subscribers
    this._actions = Object.create(null)
    this._actionSubscibers = []
    
    // 从根 module 开始,递归建立 module 树
    this._modules = new ModuleCollection(options)
    
    // 存储所有 namepaced module,namepace -> module 的 map
    this._modulesNamespaceMap = Object.create(null)
    
    // 用于实现 store.watch api
    this._watcherVM = new Vue()
    
    const store = this
    const { dispatch, commit } = this
    
    this.dispatch = function boundDispatch(type, payload) {
      // 绑定了当前 store 实例为 this 值的 dispatch 函数
      return dispatch.call(store, type, payload)
    }
    this.commit = function boundCommit(type, payload, options) {
      // 绑定了当前 store 实例为 this 值的 commit 函数
      return commit.call(store, type, payload, options)
    }
    
    // 是否开启 strict 模式
    this.strict = strict
    
    // 拿到根 module 的 state,然后往上面添加子 module 的 state,最终构建整个 state 树
    const state = this._modules.root.state
    
    // 从 root module 开始,递归 init:建立 state 树,
    // 注册 getters、mutaions、actions
    // 建立 state 树的结果就是,我们可以通过 store.state 访问到任何一个 module 的 state
    // 注册那三个的结果就是,在别处调用 store.getters、store.commit、store.dispatch 时可以起作用,注册的结果体现在 _wrappedGetters、_makeLocalGettersCache、_mutaions、_actions 等属性里
    installModule(this, state, [], this._modules.root)
    
    // 使 state 和 getters 变为响应式,途径就是将前者当做 options.data.$$state 后者当做 options.computed 传入 new Vue 生成一个 vm 实例赋给 store._vm
    resetStoreVM(this, state)
    
    // 加载插件
    plugins.forEach(plugin => plugin(this))
  }
  
  get state() {
    return this._vm._data.$$state
  }
  
  // commit 时的 hanlder 最终都会在这里的 f 中得到执行,所以这是 strict mode 实现的关键
  _withCommit(f) {
    const c = this.committing
    this.committing = true
    f()
    this.committing = c
  }
  
  // 其他的一些 api
}

综上,new Vuex.store(options) 做的事情:

  1. 初始化属性、方法和一些内部状态
  2. 创建 moduleCollection 实例(即递归创建 module 树)
  3. 递归初始化每个 rawModule 的属性:创建 state 树,注册 gettersmutationsactions
  4. stategetters 当做 options 的属性传入 new Vue() 使之变为响应式
  5. 加载 plugins

比较关键的是 2、3、4 三步:

new ModuleCollectionnew Module

class ModuleCollection {
  constructor(rawRootModule) {
    // 从根开始,递归 register 每一个 rawModule,最终建立一个 module 树
    this.register([], rawRootModule, false)
  }
  
  // 这个 path 即前面所说的代表每个 module 的唯一标识
  register(path, rawModule, runtime = true) {
    // 通过 rawModule 创建 module
    const newModule = new Module(rawModule, runtime)
    
    if(!path.length) {
      // 根 module
      this.root = newModule
    } else {
      // 非根 module,添加到其父 module 的 _children 属性里
      const parent = this.get(path.slice(0, -1))
      
      parent.addChild(path[path.length - 1], newModule)
    }
    
    // 如果 rawModule 存在子 module,递归 register 它们
    if(rawModule.modules) {
      forEachValue(rawModule.modules, (rawChildModule, key) => {
        this.register(path.concat(key), rawChildModule, runtime)
      })
    }
  }
  
  // 通过给定 path 数组,返回与之对应的 module
  get(path) {
    return path.reduce((module, key) => module.getChild(key), this.root)
  }
  
  // 通过给定 path 数组,得到与之对应的 module 的 namespace
  getNamespace (path) {
    let module = this.root
    return path.reduce((namespace, key) => {
      module = module.getChild(key)
      return namespace + (module.namespaced ? key + '/' : '')
    }, '')
  }
}

class Module {
  constructor(rawModule, runtime) {
    // runtime 的作用是,只有 runtime 为 true 的 module 可以被 unregister
    this.runtime = runtime
    this._children = Object.create(null)
    this._rawModule = rawModule
    this.state = (typeof rawModule.state === 'function' ? rawModule.state() : rawModule.state) || {}
  }
  
  get namespaced() {
    return !!this._rawModule.namespaced
  }
  
  // 只在 store.hotUpdate 时候调用
  update(rawModule) {
    // child module 单独处理
    this._rawModule.namespaced = rawModule.namespaced
    
    if(rawModule.getters) {
      this._rawModule.gettters = rawModule.getters
    }
    
    if(rawModule.actions) {
      this._rawModule.actions = rawModule.actions
    }
    
    if(rawModule.mutations) {
      this._rawModule.mutations = rawModule.mutations
    }
  }
  
  // ...
  // 其他对 _chidlren 增删查的方法
  
  forEachChild (fn) {
    // _children 的属性值是 module
    // _rawModule.module 的属性值是 _rawModule
    forEachValue(this._children, fn)
  }
  
  forEachGetters(fn) {
    forEachValue(this._rawModule.getters, fn)
  }
  
  // ...
  // 其他通过 forEachValue 遍历 _rawModule 的 mutaions、actions 的方法
}

// 接受两个参数,第一个参数为对象
// 第二个参数为一个以第一个参数的 (value, key) 为参数的函数
function forEachValue(obj, fn) {
  Object.keys(obj).forEach(key => fn(obj[key], key)))
}

可见,new ModuleCollection(options) 的作用就是创建 module 树,这个树只有一个 root 属性指向它自己。另外通过 getNamespace(path) 计算 namespace 的方法,我们可以得知:

  • 一个 namespacedmodule 会影响其所有子 modulenamespace,这些子 modulenamespace 必然包含其父 modulenamespace
  • 一个非 namepaced 的子 module,其 namespace 值与离其最近的 namespaced 的祖先 modulenamespace 相同,如果不存在这样的祖先 module,那么就与根 modulenamespace 相同,也就是 ''

module 的那些遍历方法则在 installModule 时用到。

installModule

function installModule(store, rootState, path, module, hot) {
  const isRoot = !path.length
  // 获取对应 module 的 namespace
  const namespace = store._modules.getNamespace(path)
  
  if(module.namespaced) {
    // 对于有着相同 namespace 的 namespaced 的 module,后注册的会覆盖
    store._modulesNamespaceMap[namespace] = module
  }
  
  if(!isRoot && !hot) {
    const parentState = getNestedState(rootState, path.slice(0, -1))
    const moduleName = path[path.length - 1]
    
    // 将自身的 state 添加到父 module 的 state 上,即创建 state 树
    store._withCommit(() => Vue.set(parentState, moduleName, module.state))
  }
  
  // local 为当前 module 自身的 state、getters 和添加了 namespace 的 commit 和 dispatch 的方法组成的对象
  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)
  })
  
  // 递归 install 子 module
  module.forEachChild((child, key) => {
    installModule(store, rootState, path.concat(key), child, hot)
  })
}

function makeLocalContext(store, namespace, path) {
  const noNamespace = namespace === ''
  // namespace 不为 '' 时为 type 增加 namespace
  const local = {
    commit: noNamespace ? store.commit : function(_type, _payload, _options) {
      let { payload, options, type } = unifyObjectStyle(_type, _payload, _options)
      
      if(!options || !options.root) {
        type = `${namespace}${type}`
      }
      
      store.commit(type, payload, options)
    },
    
    dispatch: noNamespace ? store.dispatch : function(_type, _payload, _options) {
      let { payload, type, options } = unifyObjectStyle(_type, _payload, _options)
      
      if(!options || !options.root) {
        type = `${namespace}${type}`
      }
    }
    
    return store.dispatch(type, payload)
  }
  
  Object.defineProperties(local, {
    getters: {
      get: noNamespace ?
      function() { return store.getters } :
      // makeLocalGetters 将 getter 定义为返回 store.getters[namespace] 的访问器属性
      function() { return makeLocalGetters(store, namespace) }
    },
    state: {
      get: function() { return getNestedState(store.state, path) }
    }
  })
}

function registerGetter(store, type, rawGetter, local) {
  if(store._wrappedGetters[type]) {
    return
  }
  
  // wrap 每个 module 的 rawGetter,然后按照其 namespace 存到 store._wrappedGetters 里
  // 为什么 wrapped 函数要以 store 为参数,store 不是已经传进来了?
  store._wrappedGetters[type] = function wrappedGetter(store) {
    // 传给每个 getter 的 4 个参数,对于非 namespaced,两个就够
    return rawGetter(local.state, local.getters, store.state, store.getters)
  }
}

function registerMutation(store, type, handler, local) {
  const entry = store._mutations[type] || (store._mutations[type] = [])
  // 将每个 namespace 对应的 handler push 到 _mutations[namespace] 里
  entry.push(function wrappedMutationHandler(payload) {
    // 传给 mutation handler 的两个参数,local.state 和 payload
    handler.call(store, local.state, payload)
  })
}

function registerAction(store, type, handler, local) {
  const entry = store._actions[type] || (store._actions[type] = [])
  // 将每个 namespace 对应的 handler push 到 _actions[namespace] 里
  entry.push(function wrappedActionHandler(payload) {
    // 传给  action handler 的两个参数
    let res = handler.call(store, {
      ...local,
      rootGetters: store.getters,
      rootState: store.state
    }, payload)
    
    if(!isPromise(res)) {
      res = Promise.resolve(res)
    } else {
      return res
    }
  })
}

installModule 的结果:

  • 创建了 state 树(上面 new ModuleCollection 只是创建了 module 树)
  • 注册了所有 modulegettersactionsmutations

其实这个时候,stateactionsmutations 已经可以用了,即 commit mutaions 和 dispatch actions 已经可以引起 state 的改变了,但也仅仅是改变而已。因为 state 还不是响应式,而且 store.getter 也还不可用,而这些工作则由 resetStoreVM 来做:

resetStoreVM

function resetStoreVM(store, state, hot) {
  const oldVm = store._vm
  
  store.getters = {}
  store._makeLocalGettersCache = Object.create(null)
  
  const wrapperedGetters = store._wrapperedGetters
  const computed = {}
  
  // fn 为 wrapperedGetters[key],前面 registerGetter 中
  // fn 为一以 store 为参数,返回 rawGetter(...) 的函数
  forEachValue(wrapperedGetters, (fn, key) => {
    // 偏函数和 closure?
    computed[key] = () => fn(store)
    Object.defineProperty(store.getters, key, {
      get() {
        return store._vm[key]
      },
      enumerable: true
    })
  })
  
  const silent = Vue.config.silent
  
  Vue.config.silent = true
  // 通过将 state 传进 data.$$state,getters 传作 computed 使二者变为响应式
  store._vm = new Vue({
    data: {
      $$state: state
    },
    computed
  })
  Vue.config.silent = silent
  
  // 开启 strict mode
  if (store.strict) {
    enableStrictMode(store);
  }
  
  // ... 其他在 new Vuex.store 时不会执行的代码
}

以上便是 new Vuex.store 过程中所有相关的代码。

这一过程执行完之后,我们大概就可以猜到 store.commit 发生作用的机制,肯定是通过 commit type 去 store._mutations 里面找对应的 handler 队列,然后遍历执行,执行的过程会丢到 store._withCommit(fn)fn 里。

store.dispatch 也是一个机制,不过它可能要麻烦很多,因为要处理异步的问题。

mapStatemapGetters 那一干 API 则是一些锦上添花的。

我发现当某个 API 支持的参数类型有超过一种时,Vue、Vuex 等的处理方式都是先 noramlize 或者 unify 一下,也就是不管传的是什么,先通过一个函数转化成统一的格式,然后真正的 handler 只用针对这统一的格式做处理。比如上面的 mapState 应对不同的参数类型的数据、commit 不同参数类型的 mutation,还有 Vue 里面对不同 vnode 的 children 的 normalize。