vuex 源码学习

版本:3.4.0

index.js

此文件导出 vuex 对象。

export default {
  Store, // Store 类
  install, // 安装钩子,vue.use() 调用
  version: '__VERSION__', // 版本
  mapState, // 辅助函数,下同
  mapMutations,
  mapGetters,
  mapActions,
  createNamespacedHelpers
}

store.js#install

// store.js
// 开头,第六行
let Vue // bind on install
……
// 最后
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)
}

如果已经注册了,就不再注册。 applyMixin 在 mixin.js 里。其会调用 vuexInit 把 store 实例挂载到每个 vue 实例上。这样,就可以通过 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) {
    this.$store = options.parent.$store
  }
}

如果 vue 实例有 store,说明是根节点,使用根节点的 store,否则使用父节点的。

// 根节点配置
new Vue({
  el: '#app',
  router,
  store,
  components: { App },
  template: '<App/>'
})

store.js#Store

vuex 模块是嵌套结构,实例化的时候,vuex 会根据 options 进行递归挂载。

this._modules = new ModuleCollection(options)

在 module/module-collection.js 里的 register 方法会进行递归注册挂载。

export default class ModuleCollection {
  constructor (rawRootModule) {
    // register root module (Vuex.Store options)
    this.register([], rawRootModule, false) // 第三个参数暂不明
  }
  
  get (path) {
    return path.reduce((module, key) => {
      return module.getChild(key)
    }, this.root)
  }

  register (path, rawModule, runtime = true) {
    const newModule = new Module(rawModule, runtime)
    if (path.length === 0) { // 根节点
      this.root = newModule
    } else { // 子模块
      const parent = this.get(path.slice(0, -1)) // 这里的 get 是用来获取 path 指定模块的
      parent.addChild(path[path.length - 1], newModule)
    }

    // register nested modules
    if (rawModule.modules) { // 这里是递归注册
      forEachValue(rawModule.modules, (rawChildModule, key) => {
        this.register(path.concat(key), rawChildModule, runtime)
      })
    }
  }
}

installModule

在 installModule 函数里,mutation、action、getter 会被注册到模块上,同时递归处理子模块。

resetStoreVM

在这个函数里会对数据进行响应式处理。

store._vm = new Vue({
  data: {
    $$state: state
  },
  computed
})

Store.commit & Store.dispatch

Store 的 commit 和 dispatch 都会先进行参数校验和转换。因为 commit 和 dispatch 都有两种传参方式。

commit(type: string, payload?: any, options?: Object)

commit(mutation: Object, options?: Object)

dispatch(type: string, payload?: any, options?: Object): Promise<any>

dispatch(action: Object, options?: Object): Promise<any>

commit 在执行 mutation 的时候,会通过 this._withCommit 设置 this._committing 这个状态。这是为了在严格模式下能够给出非 mutation 更新数据的警告。

function enableStrictMode (store) {
  store._vm.$watch(function () { return this._data.$$state }, () => {
    if (__DEV__) {
      assert(store._committing, `do not mutate vuex store state outside mutation handlers.`)
    }
  }, { deep: true, sync: true })
}

vuex 将指定类型的 mutation 和 action 都视为数组,mutation 是同步,而 action 是异步。

commit (_type, _payload, _options) {
  // check object-style commit
  const {
    type,
    payload,
    options
  } = unifyObjectStyle(_type, _payload, _options)

  const mutation = { type, payload }
  const entry = this._mutations[type]

  this._withCommit(() => {
    entry.forEach(function commitIterator (handler) {
      handler(payload)
    })
  })
}

dispatch (_type, _payload) {
  // check object-style dispatch
  const {
    type,
    payload
  } = unifyObjectStyle(_type, _payload)

  const action = { type, payload }
  const entry = this._actions[type]

  const result = entry.length > 1
    ? Promise.all(entry.map(handler => handler(payload)))
    : entry[0](payload)

  return new Promise((resolve, reject) => {
    result.then(res => {
      resolve(res)
    }, error => {
      reject(error)
    })
  })
}