基于Vuex从零实现自己的Vuez插件(一)

553 阅读2分钟

参考链接:

Vuex实现原理解析
Vuex框架原理与源码分析
vue-mixins

在之前,我们实现了一个简单版本的MVVM框架,现在我们来模拟实现一下vue中的vuex

回顾Vuex的使用

在这里,以官网的例子进行说明

use(Vuex) & 声明 store

import Vue from 'Vue'
import Vuex from 'Vuex'
Vue.use(Vuex)
const store = new Vuex.Store({
  state: {
    count: 0
  },
  mutations: {
    increment (state) {
      state.count++
    }
  }
})

在上面代码中,Vue.use(Vuex),这一句代码至关重要。那这句代码为什么重要呢?它到底做了些什么?
在说明这些问题之前,我们必须知道 Vue中的use(xx)方法会默认调用xx.install()方法。 也就是说,在Vue.use(Vuex)的时候,会执行Vuex.install()方法。

为了验证上面的观点,我们来看下Vue.use方法实现

function (plugin: Function | Object) {
  /* istanbul ignore if */
  if (plugin.installed) {
    return
  }
  // additional parameters
  const args = toArray(arguments, 1)
  args.unshift(this) 
  if (typeof plugin.install === 'function') {
    // 实际执行插件的install方法
    plugin.install.apply(plugin, args)
  } else {
    plugin.apply(null, args)
  }
  plugin.installed = true
  return this // 返回调用对象
}

我们再来看一下Vuexinstall实现

/*暴露给外部的插件install方法,供Vue.use调用安装插件*/
export function install (_Vue) {
  if (Vue) {
    /*避免重复安装(Vue.use内部也会检测一次是否重复安装同一个插件)*/
    if (process.env.NODE_ENV !== 'production') {
      console.error(
        '[vuex] already installed. Vue.use(Vuex) should be called only once.'
      )
    }
    return
  }
  /*保存Vue,同时用于检测是否重复安装*/
  Vue = _Vue
  /*将vuexInit混淆进Vue的beforeCreate(Vue2.0)或_init方法(Vue1.0)*/
  applyMixin(Vue)
}

或许,有些地方还看不太懂,比如参数_Vue的作用,但没关系,我们接着往下走...
我们知道vuex是全局共用一个store,只要一处store的数据发生了变化,全局都会发生变化。那么具体是如何实现的呢?

实现全局共享Store

如何实现全局共用一个store
解决这个问题,我们不难有这样一个思路,就是让所有的vue实例,都一个指向store对象的指针。


既然有了思路,那么具体该如何实现呢?我们知道,在使用vue开发时,vue组件是分层级的,类似于结构。 这里写图片描述 根据这个思路,我们不难想到递归。 图片描述

使用递归可以实现我们的目的,但在这里,我们仿照Vuex源码的方式,使用Vue.mixin的方式。使用mixin的方式,比使用递归效果更好,姿势也更华丽...
关于mixin可以看官网关于mixin的介绍

根据官网的例子介绍,全局混入的方式会影响每个单独创建的Vue实例 (包括第三方组件)。

// 为自定义的选项 'myOption' 注入一个处理器。
Vue.mixin({
  created: function () {
    var myOption = this.$options.myOption
    if (myOption) {
      console.log(myOption)
    }
  }
})

new Vue({
  myOption: 'hello!'
})
// => "hello!"

看到这里,我们可以想到利用mixin的方式,为所有的Vue实例绑定同一个store对象

前面我们提到install方法,这个方法是使用Vuex必须要执行的,我们可以在这里做一些手脚,加入一些自己的逻辑。


要为所有的Vue实例绑定store对象,我们要思考以下几个问题:

  1. store从哪里来?
  2. 如果确保所有的store是同一个对象?
  3. 什么时候进行绑定?

为了回答上面三个问题,我们再来Vuex的使用
store.js

import Vue from 'Vue'
import Vuex from 'Vuex'
Vue.use(Vuex)
const store = new Vuex.Store({
  state: {
    count: 0
  },
  mutations: {
    increment (state) {
      state.count++
    }
  }
})

main.js

import Vue from 'vue'
import App from './App'
import './common/css/iconfont.css'
import router from './router/index'

new Vue({
  el: '#app',
  router,
  store,  // 在这里,我们传入了store
  render: h => h(App)
})

现在,回答一下我们的问题store在我们创建vue根实例时传入。我们只要让所有的vue实例的全部都指向根实例的store就好了。还有,前面我们提到过,vue中的组件时表现为树形结构的,我们可以options.parent访问到当前实例的父亲。

现在只剩下最后一个问题,什么时候绑定store?
对于数据的绑定,我们推崇越早越好,因此我们利用beforeCreate生命钩子函数实现我们的目的

具体代码如下:


let Vue;  
let install = (_Vue) => { //到这里,参数_Vue的作用也就明确了,因为我们需要用到Vue
  Vue = _vue;
  Vue.mixin({
    beforeCreate() {
      const options = this.$options
      if (options.store) {
        /*存在store其实代表的就是Root节点,直接执行store(function时)或者使用store(非function)*/
        this.$store = typeof options.store === 'function'
          ? options.store()
          : options.store
      } else if (options.parent && options.parent.$store) {
        /*子组件直接从父组件中获取$store,这样就保证了所有组件都公用了全局的同一份store*/
        this.$store = options.parent.$store
      }
    }
  })
}

到目前为止,我们就实现了为所有的Vue实例绑定同一个store对象。

未完待续...