vuex手撕源码

vuex源码解析及手撕代码

项目环境

本项目借助vue-cli快速搭建,package.json内容如下

{
  "name": "myvuex2",
  "version": "0.1.0",
  "private": true,
  "scripts": {
    "serve": "vue-cli-service serve",
    "build": "vue-cli-service build"
  },
  "dependencies": {
    "core-js": "^3.8.3",
    "vue": "^2.6.14",
    "vuex": "^3.6.2"
  },
  "devDependencies": {
    "@vue/cli-plugin-babel": "~5.0.0",
    "@vue/cli-plugin-vuex": "~5.0.0",
    "@vue/cli-service": "~5.0.0",
    "less": "^4.0.0",
    "less-loader": "^8.0.0",
    "vue-template-compiler": "^2.6.14"
  }
}

首先来看看vuex是怎么使用的呢?先引入Vuex,然后使用Vue.use(Vuex)注册组件,再实例化一个Store类。所以我们在手写vuex代码时需要暴露 install 函数(Vue.use(Vuex)会自动调用 Vuex 的 install 方法)和 Store类。

import Vue from 'vue'
import Vuex from '../vuex'

Vue.use(Vuex)
export default new Vuex.Store({
  namespaced: true,
  state: {
  },
  getters: {
  },
  mutations: {
  },
  actions: {
  },
  modules: {
  }
})

这个文件主要是整合内容并暴露给外界,暴露了 Store 类和 install 方法。

index.js

import { Store, install } from './store';
export default {
  Store,
  install
}

下面是vuex的核心部分,定义了 Store 类和 install方法。

import { forEach } from "./util";

可以看到在文件开头引入了 forEach,他是我们写的一个工具函数,作用是遍历一个对象中的所有的函数,并返回该函数的函数体和函数名。

export const forEach = (obj = {}, fu) => {
  Object.keys(obj).forEach(key => fu(obj[key], key))
}

接下来就是Store类的构造函数中会分别调用工具函数 forEach 将传入的 getters、mutattions、actions 添加到对象上。同时定义 commit 函数,使实例可以调用 mutatiaons 中的方法,定义 dispatch 函数,使实例可以调用 actions 中的方法。

store.js

import { forEach } from "./util";
import applyMixin from "./mixin";
let Vue;
class Store{
  constructor(options) {
    // 保存getters
    this.getters = {};
    const computed = {};
    // 遍历得到所有getters
    forEach(options.getters, (fu, fuName) => { 
      // 增加缓存机制
      computed[fuName] = () => {
        return fu(this.state);
      }
      Object.defineProperty(this.getters, fuName, {
        get: () => this._vm[fuName]
      })
    })
    // 发布订阅者模式,将用户定义的mutation和actions先保存起来,当调用commit十九调用订阅的mutation,调用dipatch时就调用订阅action
    this._mutations = {};
    forEach(options.mutations, (fu, fuName) => {
      this._mutations[fuName] = (payload) => fu.call(this, this.state, payload);
    })
      
    this._actions = {};
    forEach(options.actions, (fu, fuName) => {
      this._actions[fuName] = (payload) => fu.call(this, this, payload);
    })
    // 让数据变为响应式
    this._vm = new Vue({
      data: {//内部状态
        $$state: options.state
      },
      computed //计算属性会将自己的属性放到实例上
    })
  }
  // 可以使用本方法调用订阅的actions方法
  dispatch = (fuName, payload) => {
    this._actions[fuName](payload);
  }
   // 可以使用本方法调用订阅的mutation方法
  commit = (fuName, payload) => {
    this._mutations[fuName](payload);
  }
  // 类的属性访问器,当访问这个实例的state属性时会执行此方法
  get state() {
    return this._vm._data.$$state;
  }
}
// 定义的install方法
const install = (_Vue) => {
  Vue = _Vue;
  applyMixin(Vue);
}
export {
  Store,
  install
}

在上面的文件中还定义了 install 方法,该方法在执行 Vue.use(Vuex) 时会自动调用,install 方法中主要是调用 mixin 方法,使每个组件都混入一个 $store 属性,他们都指向同一个 $store 实例。这样就可以达到数据共享的目的了。

mixin.js

const applyMixin = (Vue) => {
  Vue.mixin({// 每个组件都会混入beforeCreate函数定义一个$store属性,指向同一个Sotre
    beforeCreate() {
      // 如果是根组件就为根组件添加$store属性
      if (this.$options && this.$options.store) {
        this.$store = this.$options.store;
      } else {// 如果是子组件就将根组件的$store属性值赋值给当前组件的$store属性
        this.$store = this.$parent && this.$parent.$store;
      }
    }
  })
}
export default applyMixin;

总结

vuex未实现命名空间的原理还是很简单,很好理解的,要点在于对 Vue.mixin 的方法的应用,该方法可以让所有组件共享一个 $store 实例,实现数据共享。另一要点就是对 getters、mutattions、actions 的搜集,以及state的响应式和 getter 的缓存机制十分巧妙。最后的难点是 vuex 的模块与命名空间的实现,它的嵌套模块递归处理很复杂,当然本项目里边并未涉及,感兴趣的小伙伴可以自己去看一看。

vuex手撕源码_第1张图片

你可能感兴趣的:(web前端,vuex)