从零开始 - 50行代码实现一个Vuex状态管理器

回顾下Vuex

先vue-cli工具直接创建一个项目,勾选Vuex,其他随意:

image.png

创建完毕自动安装依赖,之后启动项目,熟悉的helloworld ~ 简单写个demo运行看看,后面会逐步实现一个myVuex,来达到相同的期望运行结果:

src/store/index.js

import Vue from "vue";
import Vuex from "vuex";

Vue.use(Vuex);

export default new Vuex.Store({
  state: {
    age: 7
  },
  getters: { // 不用多说
    getAge(state) { return state.age }
  },
  mutations: { // vuex约定了对state的操作函数都放在这里,使用commit方法触发
    changeAge(state, data) {
      state.age = data ? data : ++state.age
    }
  },
  actions: { // vuex约定了异步类函数统一放在这里,dispatch方法触发
    syncChangeAge({ state, commit }, data) {
      state.age = 0
      setTimeout(() => {
        this.commit('changeAge', data) // 这里我还没弄懂待会怎么实现{commit}的读取,在真实的Vuex中这里不加this也是可以运行的
      }, 1000);
    }
  },
  modules: { /** vuex的模块化,先不实现modules功能,就不挖坑了 */ },
});

src/App.vue

<template>
  <div id="app">
    {{ showMe }}
    <button @click="$store.commit("changeAge")">increase</button>
    <button @click="$store.dispatch("syncChangeAge", 7)">reset</button>
  </div>
</template>

<script lang="js">
import Vue from "vue";
export default Vue.extend({
  name: "App",
  computed: {
    showMe() { return `我今年${this.$store.getters.getAge || "..."}岁了`; },
  }
});
</script>

运行效果如下:

image.png

说明:点击增加按钮加一岁,点击重置按钮进入loading状态1秒后又设置为7岁,现在,把stroe中引入的import Vuex from "vuex";改为自己的手动实现,达到跟这个demo一致的运行效果。

Ready Perfect

开始前还是先写出代码结构,创建 Vuex 文件夹,写入第一个index文件。

src/Vuex/index.js

class Store {
    constructor(parameters) { // vuex的核心四件套
        this.state = {}
        this.getters = {}
        this.mutations = {}
        this.actions = {}
    }
    get state() {
        return this.state
    }
    commit(fName, data) {
        this.mutations[fName].forEach(mutation => mutation(data));
    }
    dispatch(fName, data) {
        this.actions[fName].forEach(action => action(data));
    }
}

export default { Store }

这样vuex的简单结构就写完了,接下来处理对实例传入的mutation和action的收集,然后提供commit和dispatch函数来执行。

创建 install

首先 store 中先是调用了 Vue.use(Vuex),让状态管理器注入到vue中,此时需要用到混入。

mixin 参考:vue全局混入

根据vue文档描述,使用use必须提供一个install函数,Vue会作为参数传入,参考:vueUse

src/Vuex/index.js

class Store {
    .....
}

const install = (Vue) => {
    Vue.mixin({
        beforeCreate() {
            const { store = null } = this.$options
            if (store) {
                this.$store = store
            } else {
                this.$store = this.$parent && this.$parent.$store
            }
        }
    })
}

export default { Store, install }

绑定 state

在上一步创建install时引入了Vue,将其挂载到全局来创建一个实例对象,利用Vue中数据双向绑定来实现state:

src/Vuex/index.js

let _Vue

class Store {
    constructor(parameters) {
        const { state = { } } = parameters
        this.$vue = new _Vue({ // new一个Vue实例接收用户传进的state
            data: { state }
        })
        ......
    }
    get state() { // 抛出Vue实例上挂载的 state
        return this.$vue.state
    }
    ......
}

const install = (Vue) => {
    _Vue = Vue
    ......
}
.....

处理 getter

继续上面的代码

....
class Store {
    constructor(parameters) {
        .....
        bindInstall(this, parameters)
    }
    .....
}

const install = (Vue) => { .... }

const bindInstall = (store, options) => {
    // 处理getters
    const { getters } = options
    if (getters) {
        Object.keys(getters).forEach(key => {
            Object.defineProperty(store.getters, key, {
                get() {
                    return getters[key](options.state)
                }
            })
        })
    }
}

export default { Store, install }

到这里,可以将 src/store/index 中的引入改成我们自己的了:

// import Vuex from "vuex";
import Vuex from "../Vuex";
.....

将例子运行,将看到已经成功拿到store中的getter,继续完善

处理 mutations 与 actions

继续完善刚才的bindInstall代码:

....
class Store { ..... }

const install = (Vue) => { .... }

const bindInstall = (store, options) => { // 两边收集都比较相似
    const { getters, mutations, actions } = options
    if (getters) { ... }
    if (mutations) {
        Object.keys(mutations).forEach(mutationName => {
            let storeMutations = store.mutations[mutationName] || []
            storeMutations.push(data => {
                mutations[mutationName].call(store, store.state, data) // mutations中的函数第一个参数是state,第二个是值
            })
            store.mutations[mutationName] = storeMutations
        })
    }
    if (actions) {
        Object.keys(actions).forEach(actionName => {
            let storeActions = store.actions[actionName] || []
            storeActions.push(data => {
                actions[actionName].call(store, store, data) // 这里我第一个参数先直接返回了实例对象,还不知道如何实现vuex中的效果
            })
            store.actions[actionName] = storeActions
        })
    }
}
export default { Store, install }

保存,运行测试 - 和最初的demo结果一致,至此实现了核心的vuex状态管理器

以下是 Vuex/index.js 完整代码

let _Vue
class Store {
    constructor(parameters) {
        const { state = {} } = parameters
        this.$vue = new _Vue({ data: { state } })
        this.getters = {}
        this.mutations = {}
        this.actions = {}
        bindInstall(this, parameters)
    }
    get state() { return this.$vue.state }
    commit(fName, data) { this.mutations[fName].forEach(mutation => mutation(data)) }
    dispatch(fName, data) { this.actions[fName].forEach(action => action(data)) }
}
const install = (Vue) => {
    _Vue = Vue
    Vue.mixin({
        beforeCreate() {
            const { store = null } = this.$options
            this.$store = store ? store : this.$parent ? this.$parent.$store : null
        }
    })
}
const bindInstall = (store, options) => {
    const { getters, mutations, actions } = options
    if (getters) {
        Object.keys(getters).forEach(key => {
            Object.defineProperty(store.getters, key, {
                get() { return getters[key](options.state) }
            })
        })
    }
    if (mutations) {
        Object.keys(mutations).forEach(mutationName => {
            let storeMutations = store.mutations[mutationName] || []
            storeMutations.push(data => { mutations[mutationName].call(store, store.state, data) })
            store.mutations[mutationName] = storeMutations
        })
    }
    if (actions) {
        Object.keys(actions).forEach(actionName => {
            let storeActions = store.actions[actionName] || []
            storeActions.push(data => { actions[actionName].call(store, store, data) })
            store.actions[actionName] = storeActions
        })
    }
}
export default { Store, install }
posted @ 2021-06-19 12:27  茶无味的一天  阅读(3)  评论(0编辑  收藏  举报  来源