前端 / 技术 · 2021年8月16日 0

Vuex源码之如何实现注入?

广告位招租 (vx: ghostcode, 备注:网站广告)

Vuex 是什么?

Vuex is a state management pattern + library for Vue.js applications. It serves as a centralized store for all the components in an application, with rules ensuring that the state can only be mutated in a predictable fashion.


Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式。它采用集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化。


一个库、框架的出现肯定是为了解决某一种问题,Vuex 也不例外,它主要解决了以下两种:

  • 多个视图依赖于同一状态。
  • 来自不同视图的行为需要变更同一状态。

它是把组件的共享状态抽取出来,以一个全局单例模式来管理。原理图如下:

当然 Vuex 肯定也不是适用于所有的场景,因为它带来了一些它自己的概念:State、Getters、Mutations、Actions、Modules 。若你的项目够简单,建议还是不要用 Vuex。
源码分析:我们从 Vuex 的使用方法开始入手:
初始化:

import Vue from 'vue'
import Vuex from 'vuex'

Vue.use(Vuex)

const store = new Vuex.Store({
  state: {
    count: 0
  },
  mutations: {
    increment (state) {
      state.count++
    }
  }
})

从根组件注入 store:

new Vue({
  el: '#app',
  store: store,
})

从上面可以看出是通过 Vue.use 来使用 Vuex 的那 Vue.use 是什么呢?作用主要是来使用 Vue 的插件的。源码如下:

export function initUse (Vue: GlobalAPI) {
  Vue.use = function (plugin: Function | Object) {
    const installedPlugins = (this._installedPlugins || (this._installedPlugins = []))
    // 防止多次 use
    if (installedPlugins.indexOf(plugin) > -1) {
      return this
    }

    // additional parameters
    const args = toArray(arguments, 1)
    args.unshift(this)
    // 根据 plugin 类型的不同执行不同逻辑。1.对象的时候,则执行它身上的intall 方法,2. plugin 函数时则直接执行
    if (typeof plugin.install === 'function') {
      plugin.install.apply(plugin, args)
    } else if (typeof plugin === 'function') {
      plugin.apply(null, args)
    }
    installedPlugins.push(plugin)
    return this
  }
}

那 Vuex 既然是通过 Vue.use 调用的,那 Vuex 是要满足调用条件的,看下源码( 2.4.0 版本):

let Vue;

class Store{
    ...
}

function install(_Vue){
  // 这里也是防止多次调用 Vue.use   
  if (Vue) {
    console.error(
      '[vuex] already installed. Vue.use(Vuex) should be called only once.'
    )
    return
  }
  Vue = _Vue
  applyMixin(Vue)
}

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

源码中看出确实包括 install 方法,它里面很简单只是调用了 applyMixin 方法:

export default function (Vue) {
  const version = Number(Vue.version.split('.')[0])

  if (version >= 2) {
    // Vue 2.x 使用 mixin 方法 把 vuexInit 混入到 beforeCreate 钩子中  
    Vue.mixin({ beforeCreate: vuexInit })
  } else {
    ...
  }

  function vuexInit () {
     ...
  }
}

从上看出 install 方法根据 Vue 版本不同把 vuexInit 混入到组件中,Vuex 的使用是把实例化的 store 以选项的形式放到 Vue 根组件中,那子组件是如何获得 store 的呢?那就要依靠 vuexInit 了:

function vuexInit () {
    const options = this.$options
    
    if (options.store) {
      // 注入到根组件  
      this.$store = typeof options.store === 'function'
        ? options.store()
        : options.store
    } else if (options.parent && options.parent.$store) {
      // 子组件 $store 引用父组件的 $store ,保证全部使用的是同一个 store 对象。  
      this.$store = options.parent.$store
    }
  }

主要区分了根组件和子组件是如何获取 store 对象的。

至此 Vuex 注入 Vue 组件就讲解完了,总结:关键点是利用 mixin 方法混入一个方法,然后方法内部根据 options.parent 来一层层引用父级 store 属性。