手写简版Vuex

217 阅读1分钟

1、简版Vuex实现思路

和实现简版Vue Router类似,实现简版的Vuex,可以按照下面的步骤来:

  • MyVuex是一个插件:以Vue.use的方式安装

  • 和官方Vuexconst store = Vuex.Store({...})使用方法类型,MyVuex最终导出一个对象,并提供MyStore属性`

  • state,getters, mutations和actions这四个核心概念的实现:

    • state: 设为Vue的data, 实现响应式
    • getters: 只读属性,不可修改
    • mutations和actions都是对象的方法,只需执行对于的方法函数即可
  • commit和dispatch的实现

    • commit的参数是state
    • dispatch的参数是ctx({state, getters, commit, dispatch...})
  • 把MyStore选项混入到原型上

2、实现简版Vuex

按照上面的实现思路,实现简版Vuex的完整代码如下所示:

// stote/my-vuex.js
let Vue

function install(_Vue) {
    Vue = _Vue
    // 这样store执行的时候,就有了Vue,不用import 
    // 这也是为啥Vue.use必须在新建store之前
    Vue.mixin({
        beforeCreate() {
            // 根组件才把store选项混入到Vue原型上
            if (this.$options.myStore) {
                Vue.prototype.$myStore = this.$options.myStore
            }
        }
    })
}

class MyStore {
    constructor(opts = {}) {
        this.$opts = opts
        // 利用vue数据响应
        this.state = new Vue({
            data: this.$opts.state
        })
        // 初始化getters, mutations和actions
        this.$opts.getters && this.handlerGetters(this.$opts.getters)
        this.mutations = this.$opts.mutations || {}
        this.actions = this.$opts.actions || {}
    }
    handlerGetters(getters) {
        this.getters = {}
        // 定义只读的属性
        Object.keys(getters).forEach(key => {
            Object.defineProperty(this.getters, key, {
                get: () => {
                    // 执行getters中的方法
                    return getters[key](this.state)
                }
            })
        })
    }
    // 触发mutaions,需要实现commit
    commit = (type, arg) => {
        this.mutations[type](this.state, arg)
    }
    // 触发actions,需要实现dispatch
    dispatch = (type, arg) => this.actions[type]({ commit: this.commit, state: this.state, getters: this.getters, dispatch: this.dispatch }, arg)

}

export default { MyStore, install }
// stote/test-my-vuex.js
import Vue from 'vue'
import MyVuex from './my-vuex'

Vue.use(MyVuex)

export default new MyVuex.MyStore({
    state: {
        myCount: 0
    },
    getters: {
        myLeft(state) {
            return 10 - state.myCount
        },

    },
    mutations: {
        myAdd(state) {
            state.myCount += 1
        },
    },
    actions: {
        myAdd({getters, commit}) {
            if (getters.myLeft > 0) {
                commit('myAdd')
                return true
            }
            return false
        },
        myAsyncAdd({dispatch}) {
            return new Promise(resolve => {
                setTimeout(() => {
                    resolve(dispatch('myAdd'))
                }, 1000)
            })
        },
    },
})
// main.js
import Vue from 'vue'
import App from './App.vue'
import myStore from './store/test-my-vuex'

Vue.config.productionTip = false

new Vue({
  myStore,
  render: h => h(App)
}).$mount('#app')
<!--TestMyVuex.vue-->
<template>
  <div>
    <h3>测试我手写的简版Vuex</h3>
    <p>手榴弹扔了{{$myStore.state.myCount}}</p>
    <p>手榴弹还剩{{$myStore.getters.myLeft}}</p>
    <button @click="Throw">扔一个手榴弹</button>
    <button @click="asyncThrow">蓄力扔一个手榴弹</button>
  </div>
</template>
<script>
export default {
  methods: {
    Throw() {
      const result = this.$myStore.dispatch("myAdd");
      if (!result) {
        alert("没手榴弹了");
      }
    },
    async asyncThrow() {
      const result = await this.$myStore.dispatch("myAsyncAdd");
      if (!result) {
        alert("没手榴弹了");
      }
    },
  }
};
</script>