【源码解析】Vuex原理1,揭秘vuex是如何给每个组件挂载$store的

1,250 阅读4分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第14天,点击查看活动详情

前言

大家好,在之前的一篇文章Vuex的使用介绍中我们分享了关于Vuex的一些基本用法介绍,最后还自己动手手写了个简单的Vuex,但对其实现原理并没有做一个详细的分析和介绍。那么今天这篇分享中我们将一起来学习下Vuex的源码,看一看Vuex是如何实现的。

为每个组件添加$store

我们知道不管是在哪个组件中,只要通过组件的实例this都能够访问到一个名为$store的属性,实际上该属性中保存的就是Store的实例,那么它是如何挂载到每个组件实例上的呢?我们先来看下Vuex的用法,然后根据用方法再来反推一下

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

Vue.use(Vuex);
const store = new Vuex.Store({
...
});
  • 首先我们看到Vuex需要通过Vue的use方法安装一下,在前面分享use方法的原理时有提到:
    • 如果传给use的参数是一个函数,则在use中直接将这个函数执行
    • 如果传递的参数是一个对象,那么这个对象必须要包含一个install方法,然后在use中会调用该对象的install方法
  • 接着下面代码在创建Store的实例时是通过new Vuex.Store创建的 综合以上两点就说明:Vuex是一个对象,在该对象中有个install函数和一个Store类(构造函数)下面我们就打开Vuex的源码来验证一下我们的猜测。

install

以下源码均基于vuex3.2.0版本解析

// src/store.js  523行
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是否存在(这里的Vue并不是我们使用的Vue类而是前面声明的一个变量,但最终该变量保存的还是Vue类),并且判断Vue和_Vue是否相等(_Vue其实就是Vue类,是通过调用install函数传进来的)
    • 如果说Vue变量已经被赋过值,并且Vue与_Vue是相等的,就抛出一个错误提示(Vuex已经安装过了)
    • 因此初始Vuex没被安装时Vue是undefined,当调用了install方法对Vuex安装后就会将_Vue赋值给Vue
  • 如果执行到下面的代码,说明Vuex还没有安装,则将_Vue赋值给变量Vue
  • 最后调用了一个applyMixin方法

applyMixin

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

  if (version >= 2) {
    Vue.mixin({ beforeCreate: vuexInit })
  } else {
    //兼容vue1.0相关代码,这里就直接省略了
    }
  }

  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的版本,如果vue是2.0以上的版本,则直接调用Vue.mixin方法混入了一个beforeCreate周期函数。如果是2.0以下的版本则进行单独的处理,因为vue1.0不支持Vue.mixin混入方法
    • 另外我们在前面分享mixin原理时就有提到:mixin全局混入的一个最大特点就是能够将属性或方法混入到所有组件中去。这也很好的解答了我们前面所提出的问题($store是如何挂载到每个组件上的)
  • 继续往下看,这里只是调用了一下Vue的混入方法mixin,而混入的核心操作都在这个vuexInit方法中
  • 在vuexInit中,首先获取到当前组件实例的options属性,我们在前面的某篇文章分享中有提到:每个组件的实例上都会有个$options属性,因为这里混入的是beforeCreate周期函数,而在该函数中的this指向的就是当前组件的实例
  • 然后检测options中是否存在store属性,如果已经存在则直接给当前实例挂载一个$store属性,并将options中的store赋值给组件实例上的¥store
    • 如果options中的store是一个函数,则先将函数执行然后把返回结果赋值给$store
    • 否则就直接赋值给$store
  • 如果说options中不存在这个store属性,则会通过options的parent属性到当前组件的父组件中去找到$store,然后将其挂载到当前实例上。
    • 这里如果当前组件中没有$store属性,那么当前组件的父组件中一定是有的,这是因为我们在初始化vue的时候会给根组件的options挂载一个store属性,而beforeCreate周期函数的执行顺序又是先父后子,也就是说先从根组件开始依次向后代组件进行混入,从而也就是说父组件肯定会优先挂载¥store属性。所以才会到父组件中去获取。

总结

本次分享我们通过对Vuex的初始化代码反推出Vuex是一个对象并且该对象中至少包含了一个install方法和一个Store类,紧接着通过对源码的分析验证了我们的猜测是对的,并且从源码的分析中也解答了我们最初提出的疑问,简单总结如下:

就是通过Vue.use安装Vuex时会调用Vuex中的install函数,而在该函数中又调用了个applyMixin函数,在applyMixin函数中通过Vue.mixin做了一个全局混入,给每个组件混入了一个beforeCreate周期函数,当组件执行到该周期函数中就会给当前组件挂载一个$store属性,并赋值为当前组件的options中的store属性,如果该属性不存在就到当前组件的父组件中寻找并赋值

以上就是给每个组件添加$store的实现原理,后面我们还将继续分析Vuex中commit/dispatch等方法的实现原理,今天的分享就先到这里了。