Vuex原理解析

461 阅读3分钟

这是我参与11月更文挑战的第21天,活动详情查看:2021最后一次更文挑战

TIP 👉 休对故人思故国,且将新火试新茶。诗酒趁年华。____苏轼《望江南·超然台作》

前言

Vuex 原理

vuex 核心原理分析

源代码地址

我们不直接硬生生的去分析源码,现在希望大家抛掉对 vuex 的所有记忆,让我们回到几年前vuex诞生的那个时间点,从头开始去思考基于 FLUX 思想,如何打造一个我们自己的状态管理工具

a. 站在FLUX 的角度去思考

在开发中面临最多的场景是状态重复但是不集中

// a.vue
<h1>{{ username }}</h1>

// b.vue
<h2>
  {{ username }}
</h2>

/**
* 如果 username 需要在每个组件都获取一次,是不是很麻烦,虽然可以通过共同的父级传入,但是不都是这种理想情况
*/

这里其实就出现了状态重复的问题,在不同的组件中依赖了同样的状态,重复就会导致不对等的风险,基于 FLUX 的思想,我们设计的状态管理将是中心化的工具,也就是集中式存储管理应用的所有组件的状态,大白话,就是将所有的状态放在一个全局的 Tree 结构中,集中放在一起的好处是可以有效避免重复的问题,也更好的管理,将状态和视图层解耦

TJ 原来说过我的状态管理就是 {},对中心化的管理工具来说,不就是这样嘛😂

b. 如何去更改状态

自由即代表了混乱,FLUX 推崇以一种可预测的方式发生变化,而且有且唯一一种,这样的好处是所有的行为可预测,可测试,对于之后做个 dev-tool 去调试、时间旅行都很方便,现在的问题就是要去思考同步和异步的问题了,为了区分的更清楚,我们定义两种行为,Actions 用来处理异步状态变更(内部还是调用 Mutations),Mutations 处理同步的状态变更,整个链路应该是一个闭环,单向的,完美契合 FLUX 的思想

「页面 dispatch/commit」-> 「actions/mutations」-> 「状态变更」-> 「页面更新」-> 「页面 dispatch/commit」...

c. 如何和 vue 集成呢?

插件呀~这样可以和 vue 集成在一起,通过 mixin$store 这样的快速访问 store 的快捷属性注入到每一个 vue 实例中

开发 vue 插件

d. 怎么让 store 是响应式的呢?

利用 vue data 里的状态是响应式的啊~

e. 开始撸码

Step1 - store 注册

/**
* store.js - store 注册
*/
let Vue

// vue 插件必须要这个 install 函数
export function install(_Vue) {
  // 拿到 Vue 的构造器,存起来
  Vue = _Vue
  // 通过 mixin 注入到每一个vue实例 👉 https://cn.vuejs.org/v2/guide/mixins.html
  Vue.mixin({ beforeCreate: vuexInit })
  
  function vuexInit () {
    const options = this.$options
    // 这样就可以通过 this.$store 访问到 Vuex 实例,拿到 store 了
    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
    }
  }
}

Step2 - 响应式

/**
* store.js - 实现响应式
*/
export class Store {
  constructor(options = {}) {
    resetStoreVM(this, options.state)
  }
  
  get state () {
    return this._vm._data.$$state
  }
}

function resetStoreVM(store, state) {
  // 因为 vue 实例的 data 是响应式的,正好利用这一点,就可以实现 state 的响应式
  store._vm = new Vue({
    data: {
      $$state: state
    }
  })
}

Step3 - 衍生数据

/**
* store.js - 衍生数据(getters)
*/
export class Store {
  constructor(options = {}) {
    
    const state = options.state
    
    resetStoreVM(this, state)
    
    // 我们用 getters 来收集衍生数据 computed
    this.getters = {}
    
    // 简单处理一下,衍生不就是计算一下嘛,传人 state
    _.forEach(this.getters, (name, getterFn) => {
      Object.defineProperty(this.getters, name, {
        get: () => getterFn(this.state)
      })
    })
  }
  
  get state () {
    return this._vm._data.$$state
  }
}

function resetStoreVM(store, state) {
  store._vm = new Vue({
    data: {
      $$state: state
    }
  })
}

Step4 - Actions/Mutations

/**
* store.js - Actions/Mutations 行为改变数据
*/
export class Store {
  constructor(options = {}) {
    
    const state = options.state
    
    resetStoreVM(this, state)
    
    this.getters = {}
    
    _.forEach(options.getters, (name, getterFn) => {
      Object.defineProperty(this.getters, name, {
        get: () => getterFn(this.state)
      })
    })
    
    // 定义的行为,分别对应异步和同步行为处理
    this.actions = {}
    this.mutations = {}
    
    _.forEach(options.mutations, (name, mutation) => {
      this.mutations[name] = payload => {
        // 最终执行的就是 this._vm_data.$$state.xxx = xxx 这种操作
        mutation(this.state, payload)
      }
    })
    
    _.forEach(options.actions, (name, action) => {
      this.actions[name] = payload => {
        // action 专注于处理异步,这里传入 this,这样就可以在异步里面通过 commit 触发 mutation 同步数据变化了
        action(this, payload)
      }
    })
  }
  
  // 触发 mutation 的方式固定是 commit
  commit(type, payload) {
    this.mutations[type](payload)
  }
  
  // 触发 action 的方式固定是 dispatch
  dispatch(type, payload) {
    this.actions[type](payload)
  }
  
  get state () {
    return this._vm._data.$$state
  }
}

function resetStoreVM(store, state) {
  store._vm = new Vue({
    data: {
      $$state: state
    }
  })
}

Step5 - 分形,拆分出多个 Module

// module 可以对状态模型进行分层,每个 module 又含有自己的 state、getters、actions 等

// 定义一个 module 基类
class Module {
	constructor(rawModule) {
    this.state = rawModule || {}
    this._rawModule = rawModule
    this._children = {}
  }
  
  getChild (key) {
    return this._children[key]
  }
  
   addChild (key, module) {
    this._children[key] = module
  }
}

// module-collection.js 把 module 收集起来
class ModuleCollection {
  constructor(options = {}) {
    this.register([], options)
  }
  
  register(path, rawModule) {
    const newModule = new Module(rawModule)
    if (path.length === 0 ) {
      // 如果是根模块 将这个模块挂在到根实例上
      this.root = newModule
    }
    else {
      const parent = path.slice(0, -1).reduce((module, key) => {
        return module.getChild(key)
      }, this.root)
      
      parent.addChild(path[path.length - 1], newModule)
    }
    
    // 如果有 modules,开始递归注册一波
    if (rawModule.modules) {
      _.forEach(rawModule.modules, (key, rawChildModule) => {
        this.register(path.concat(key), rawChildModule)
      })
    }
  }
}

// store.js 中
export class Store {
  constructor(options = {}) {
    // 其余代码...
    
    // 所有的 modules 注册进来
    this._modules = new ModuleCollection(options)
    
    // 但是这些 modules 中的 actions, mutations, getters 都没有注册,所以我们原来的方法要重新写一下
    // 递归的去注册一下就行了,这里抽离一个方法出来实现
    installModule(this, this.state, [], this._modules.root);
  }
}

function installModule(store, state, path, root) {
  // getters
  const getters = root._rawModule.getters
  if (getters) {
    _.forEach(getters, (name, getterFn) => {
      Object.defineProperty(store.getters, name, {
        get: () => getterFn(root.state)
      })
    })
  }
  
  // mutations
  const mutations = root._rawModule.mutations
  if (mutations) {
    _.forEach(mutations, (name, mutation) => {
      let _mutations = store.mutations[name] || (store.mutations[name] = [])
      _mutations.push(payload => {
        mutation(root.state, payload)
      })
      
      store.mutations[name] = _mutations
    })
  }
  
  // actions
  const actions = root._rawModule.actions
  if (actions) {
    _.forEach(actions, (name, action) => {
      let _actions = store.actions[name] || (store.actions[name] = [])
      _actions.push(payload => {
        action(store, payload)
      })
      
      store.actions[name] = _actions
    })
  }
  
  // 递归
  _.forEach(root._children, (name, childModule) => {
    installModule(this, this.state, path.concat(name), childModule)
  })
}

Step6 - 插件机制

(options.plugins || []).forEach(plugin => plugin(this))

以上只是以最简化的代码实现了 vuex 核心的 state module actions mutations getters 机制。

「欢迎在评论区讨论」

希望看完的朋友可以给个赞,鼓励一下