关于前端:简易版Vuex源码实现

Vuex应用

1、注册Vuex插件

import Vuex from 'vuex'
Vue.use(Vuex)

2、创立Store实例

export default new Vuex.Store({
  state: {
    count: 0
  },
  mutations: {
    add(state, payload) {
      state.count += payload
    } 
  },
  actions: {
    delayAdd(context, payload) {
      setTimeout(() => {
        context.commit("add", payload)
      }, 1000)
    }
  },
  getters: {
    doubleCount(state) {
      return state.count * 2
    }
  }
})

3、挂载到Vue根实例中

new Vue({
  render: h => h(App),
  store
}).$mount('#app')

4、组件中应用

this.$store.state.count
this.$store.getters.doubleCount
this.$store.commit('add', 1)
this.$store.dispatch('')

总结,Vuex须要做以下这些事

  • 实现install办法
  • 在Vue根实例化时,给Vue根实例挂载store对象
  • store.state和getter都是数据响应式,并且不能间接批改的state和getter的值,只能通过commit和dispatch
  • 实现commit,同步批改state的值
  • 实现dispatch,反对异步批改state的值

源码实现

let Vue
class Store {
  constructor(options) {
    this._vm = null
    this._mutations = options.mutations
    this._actions = options.actions
    this._wrappedGetters = options.getters
    this.getters = {}
    const storeComputed = {}
    const self_store = this
    Object.keys(this._wrappedGetters).forEach((key) => {
      // 遍历取出getters的每个办法
      const gettersFn = self_store._wrappedGetters[key]
      // 转换为computed, 即返回一个无参数的高阶函数
      storeComputed[key] = () => {
        return gettersFn(self_store.state)
      }
      // 定义getter为 只读属性,并且可枚举
      Object.defineProperty(self_store.getters, key, {
        get: () => self_store._vm[key],
        enumerable: true
      })
    })
    // new一个新的Vue实例_vm,并在_vm做数据响应式解决
    this._vm = new Vue({
      data: {
        $$state: options.state // 加$$示意不代理到Vue实例属性上
        // Vue初始化在对数据做响应式解决时,还会将数据代理到实例
      },
      computed: {
        ...storeComputed
      }
    })
    console.log(this._vm)
  }
  get state () {
    return this._vm._data.$$state
  }
  set state(v) {
    throw new Error(`use store.replaceState() to explicit replace store state.`)
  }
  // 这里须要应用箭头函数 避免this扭转
  // 或者应用this.commit = this.commit.bind(this) 绑定this
  commit = (type, payload) => {
    // 依据type 从 mutations外面找对应的办法
    const fn = this._mutations[type]
    if (!fn) return
    fn.call(this, this.state, payload)
  }
  dispatch  = (type, payload) => {
    // 依据type 从 actions外面找对应的办法
    const fn = this._actions[type]
    if (!fn) return
    fn.call(this, {commit: this.commit, state: this.state}, payload)
  }
}
function install(vue) {
  /* 
     1、判断是否装置过插件
     2、保留vue构造函数
     3、通过混入的形式,在根实例创立时挂载store实例
    */
    //  1、判断是否装置过插件
    if (Store.installed) {
      return
    }
    Store.installed = true
    //2、保留vue构造函数
    Vue = vue
    //通过混入的形式,在根实例创立时挂载store实例
    Vue.mixin({
      beforeCreate() {
        if (this.$options.store) {
          Vue.prototype.$store = this.$options.store
        }
      },
    })
}
export default { Store, install }

源码实现思路

  • 执行install办法时,通过Vue.mixin混入的形式,在Vue实例化时,在beforeCreate生命周期里,给Vue原型挂载store对象Vue.prototype.$store = store
  • Store构造函数里保留传入的配置选项,便于获取state、mutations、actions、getters等
  • store.state实现:通过new一个新的Vue实例_vm, 把state存于_vm.data,state就变为响应式数据。重写state的set和get,避免间接批改state的值,只能通过commit和dispatch批改state的值,get则从_vm.data获取
  • commit办法实现:commit须要绑定this为store实例,避免dispatch调用是this指向扭转。通过传入的参数type,从mutations里取出函数执行,并传入state参数
  • dispatch办法实现:与commit办法相似,通过传入的参数type,从actions里取出函数执行,并将commit办法和state存于context作为参数传入
  • getters实现:遍历getters的属性,都转换为_vm的computed外面,即返回无参的高阶函数,并应用Object.defineProperty为每个key从新定义get办法,无需定义set,避免间接批改getter的值

评论

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

这个站点使用 Akismet 来减少垃圾评论。了解你的评论数据如何被处理