「手写实现」Vuex

315 阅读1分钟

vuex.jpeg

前言

Vuex集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以可预测的方式发生变化。

核心概念

  • State状态 数据 保存应用的状态

    export default new Vuex.Store({
      state: { count: 0 }
    })
    
  • Mutations 更改状态的函数 用于修改状态

    export default new Vuex.Store({
      mutations: {
        add(state) {
          state.count++
        }
      }
    })
    
  • Getters 类似计算属性

    export default Vuex.Store({
      getters: {
        doubleCount(state) {
          return state.count * 2
        }
      }
    })
    
  • Actions 异步操作 添加业务逻辑

    export default new Vuex.Store({
      actions: {
        add({ commit }) {
          setTimeout(() => {
            commit('add')
          }, 1000)
        }
      }
    })
    
  • store包含以上概念的容器

需求分析

  • State的数据响应式
  • Getters只读的state
  • Actions可以获取commit. state, dispatch等方法
  • Mutations可以获取state进行数据变更

任务

  • 实现一个插件

    • 实现Store类

      • 维持一个响应式状态的state
      • 实现commit
      • 实现dispatch
      • 实现getters
    • 实现install方法

      • 注册全局方法$store

实现

  1. 实现Store类, install方法并注册全局$store方法

    类要想实现插件需要内部有install方法

    // 引用构造函数,一会注册方法
    let Vueclass Store {
      
    }
    ​
    // 实现install方法
    function install(_Vue) {
      Vue = _Vue
      
      // 为什么要⽤混⼊⽅式写?主要原因是use代码在前,Store实例创建在后,⽽install逻辑⼜需要⽤到该实例
      Vue.mixin({
        beforeCreate() {
          if (this.$options.store) {
            Vue.prototype.$store = this.$options.store
          }
        },
      })
    }
    // 注:Store的使用的方法是Vuex.Store,所以包一层
    export default { Store, install }
    
  2. 保存用户配置

    // 在Store类中保存配置
    class Store {
      constructor(options) {
        this._mutations = options.mutations || {}
        this._actions = options.actions || {}
        this._wapperGetter = options.getters || {}
      }
    }
    
  3. 实现state响应式,并限制外部只读不可更改

    class Store {
      constructor(options) {    
        // 实现响应式
        this._vm = new Vue({
          data: {
            // 设置$$state为了防止外部直接访问
            $$state: options.state,
          }
        })
      }
      
      get state() {
        // 可以从Vue实例中的_data中取到响应式的$$state
        return this._vm._data.$$state
      }
    ​
      set state(v) {
        // 修改抛出错误
        console.error('please use replaceState to reset state')
      }
    }
    
  4. 实现commit方法, dispatch方法

    class Store {
      constructor(options) {
        this._mutations = options.mutations || {}
        this._actions = options.actions || {}
        this._wapperGetter = options.getters || {}
        
        // 绑定this,如果在setTimeout中使用this指向就有问题
        this.commit = this.commit.bind(this)
        this.dispatch = this.dispatch.bind(this)
      }
      
      // commit方法
      commit(type, payload) {
        const entry = this._mutations[type]
    ​
        if (entry) entry(this.state, payload)
      }
      
      // dispatch方法
      dispatch(type, payload) {
        const entry = this._actions[type]
    ​
        if (entry) entry(this, payload)
      }
    }
    
  5. 实现getters方法

    class Store {
      constructor(options) {
        this._wapperGetter = options.getters || {}
        
        // getters
        const computed = {}
        this.getters = {}
        const store = this
        
        Object.keys(this._wapperGetter).forEach((key) => {
          const fn = this._wapperGetter[key]
    
          // 同名方法执行函数并传入state
          computed[key] = function() {
            return fn(store.state)
          }
    
          // 给getters判断只读的属性
          Object.defineProperty(store.getters, key, {
            get: () => store._vm[key],
          })
        })
        
        
        // 实现响应式
        this._vm = new Vue({
          data: {
            // 设置$$state为了防止外部直接访问
            $$state: options.state,
          },
          computed
        })
      }
    }
    

最终实现代码

let Vue

// 实现Store类
class Store {
  constructor(options) {
    // 保存用户配置
    this._mutations = options.mutations || {}
    this._actions = options.actions || {}
    this._wapperGetter = options.getters || {}

    // 定义计算属性
    const computed = {}
    this.getters = {}
    // 绑定this
    const store = this

    Object.keys(this._wapperGetter).forEach((key) => {
      const fn = this._wapperGetter[key]

      // 同名方法执行函数并传入state
      computed[key] = function() {
        return fn(store.state)
      }

      // 给getters判断只读的属性
      Object.defineProperty(store.getters, key, {
        get: () => store._vm[key],
      })
    })

    // 实现响应式
    this._vm = new Vue({
      data: {
        $$state: options.state,
      },
      computed,
    })

    this.commit = this.commit.bind(this)
    this.dispatch = this.dispatch.bind(this)
  }

  get state() {
    return this._vm._data.$$state
  }

  set state(v) {
    console.error('please use replaceState to reset state')
  }

  commit(type, payload) {
    const entry = this._mutations[type]

    if (entry) entry(this.state, payload)
  }

  dispatch(type, payload) {
    const entry = this._actions[type]

    if (entry) entry(this, payload)
  }
}

// 实现插件
function install(_Vue) {
  Vue = _Vue

  Vue.mixin({
    beforeCreate() {
      if (this.$options.store) {
        Vue.prototype.$store = this.$options.store
      }
    },
  })
}

export default { Store, install }

结语

  1. state的值经过Vue进行响应式处理,并限制外部不可更改状态。
  2. commit, dispatch在配置找到方法并执行,执行的时候传入需要的值。
  3. getters使用Object.defineProperty添加只读属性。
  4. Vuex使用Vue中的响应式数据,所在它是和Vue强耦合的,这也就是只能在Vue中使用的原因。