乐趣区

关于前端:简易版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 的值
退出移动版