Vuex 源码解析(如何阅读源代码实践篇)

上一篇文章说的是如何阅读框架源代码,收到了“若是更详细一点就行了”的反馈,不如就以 Vuex 为切入点进行一次实践吧,不矫揉不造做,说走咱就走~~html

1、前提

本文假定你已经对 Vue 的使用上有必定的概念,不要求轻车熟路(使用过 Vuex 固然是最好的),但至少要了解基本的事件绑定方式,以及 Mixin 的用法,官方文档今后去前端

2、Vuex 解决了什么问题

官方的说法:Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式
这里首先要搞清楚什么是状态,状态就是数据,也就是说: Vuex 提供了一套 Vue 应用统一的数据源管理模式,除了定义数据源,还定义了数据的管理模式vue

这其中,Store 所包含的两个核心部分 State 和 Actions 分别表明了数据源,和数据的管理(操做)模式,同时做为一个全局的 VM,其有效的协调了 Vue 各组件间的通讯vuex

3、Vuex 的设计思想

若是读 Vue 文档的时候足够留心,兴许你能在插件一节找到蛛丝马迹:浏览器

插件的功能包括,经过全局 mixin 方法添加一些组件选项,如:vuexapp

也就是说,Vuex 不过是 Vue 的一个插件,经过 Mixin 的方式给每一个组件注入一个 $store 对象,因为每一个组件的 $store 指向的是同一个 store 对象(后面经过详读代码能够知道,这个 $store 实际上是一个 VM 对象),因此 store 是全局的,这就印证了以前在咱们为何须要 Vuex中的一个结论,Vuex 相似于一个事件总线框架

4、详读代码

经过 Mixin 注入 Store

从入口文件 index.js 开始,代码很少,能够直接贴出来ide

export default {
  Store,
  install,
  version: '__VERSION__',
  mapState,
  mapMutations,
  mapGetters,
  mapActions
}复制代码

若是你一眼就看出这里的关键是 install,那么你应该领略到读源码先了解设计思想的独特魅力了,没错,做为 Vue 的 Plugin,install 方法就是入口函数

循着 install 方法进入 store.js,仍是符合预期,这个方法主要干得是事情就是 mixinpost

export function install (_Vue) {
  ...
  Vue = _Vue
  applyMixin(Vue)
}

// auto install in dist mode
if (typeof window !== 'undefined' && window.Vue) {
  install(window.Vue)
}复制代码

而且还有一个小细节,浏览器环境下而且 Vue 不为空的时候,引入 Vuex 以后是会自动注册的

具体来看看 mixin.js 这个文件,划重点(注意看注释):

// 经过钩子 init / beforeCreate 执行 vuexInit
const usesInit = Vue.config._lifecycleHooks.indexOf('init') > -1
Vue.mixin(usesInit ? { init: vuexInit } : { beforeCreate: vuexInit })

// 组件初始化的时候注入 $store
function vuexInit () {
    const options = this.$options
    // store injection
    if (options.store) {
      this.$store = options.store
    } else if (options.parent && options.parent.$store) {
      this.$store = options.parent.$store
    }
}复制代码

Store 对象

Vuex 的最佳实践中,通常这样使用(带着目标去阅读,效果更佳):

// create store
const store = new Vuex.Store({
  actions: {
    ...
  },
  modules: {
    ...
  }
})
import App from './comps/app.vue'
new Vue(Vue.util.extend({ el: '#root', store }, App))复制代码

咱们须要新建一个 Store,在建立 Vue 实例的时候,做为参数传入,在上一节的 vuexInit 函数中,是从 this.$options 中取出 store 赋值给组件的 $store 的,如此,便能无缝联系上了

接下来的重点,就是 Store 这个类了,仍是 store.js 这个文件,怀着入参为 ations 和 modules 的预期,来读 constructor 方法,却是有一个语句是用来处理 modules 的

this._modules = new ModuleCollection(options)复制代码

但真的是寻寻觅觅寻不到从 options 中取出 actions 进行处理的方法,固然后面仔细阅读了 ModuleCollection 中的代码以后,才找到了答案,actions 参数也是在这里面提取的。毕竟让我纠结迷茫了良久,若是是我来写的话,我可能不会这么写,方法的命名须要有语义性,并且一个方法也应当只作一件事情

原则上为了尽快理清主流程,有些细节须要暂时略过(因此语义化的命名、合理的函数拆分,对阅读者来讲是多么的重要),假设已经知道前面的步骤已经从 options 中读到了 actions 和 modules,那么下一个核心节点就是:

installModule(this, state, [], this._modules.root)复制代码

这一步再进行分解(注意看注释)

// 注册 mutation
  module.forEachMutation((mutation, key) => {
    const namespacedType = namespace + key
    registerMutation(store, namespacedType, mutation, local)
  })

  // 注册 action
  module.forEachAction((action, key) => {
    const namespacedType = namespace + key
    registerAction(store, namespacedType, action, local)
  })

  // 注册 getter (computed)
  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)
  })复制代码

出于篇幅以及但愿阅读的同窗亲自实践的目的,具体的注册方式这里再也不展开

进入下一个重要环节 resetStoreVM,建立 VM,实现数据监听(注意看注释)

function resetStoreVM (store, state, hot) {

  // bind store public getters
  // getters 其实就是 computed
  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 实例,做为 Store 的 VM
  store._vm = new Vue({
    data: {
      $$state: state
    },
    computed
  })
  ...
}复制代码

5、小结

至此,Vuex 的主流程代码基本上算是走了一遍,看似神奇,但是代码量并不大,仍是那句话,但愿阅读的同窗可以按照这个套路本身走一遍

本文在公众号菲麦前端同步发行:

相关文章
相关标签/搜索