写点什么

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

  • 2022 年 9 月 26 日
    广东
  • 本文字数:3443 字

    阅读完需:约 11 分钟

回顾下 Vuex

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



创建完毕自动安装依赖,之后启动项目,熟悉的 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>
复制代码


运行效果如下:



说明:点击增加按钮加一岁,点击重置按钮进入 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 _Vueclass 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 }
复制代码


发布于: 刚刚阅读数: 3
用户头像

公众号:品味前端 2022.09.22 加入

一介前端,卖码为生。很惭愧,只希望在学习和分享的道路上能做一点微小的贡献。

评论

发布
暂无评论
从零开始 - 50行代码实现一个Vuex状态管理器_JavaScript_茶无味的一天_InfoQ写作社区