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

简介: 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 }
相关文章
|
22天前
|
存储 前端开发 JavaScript
前端状态管理:Vuex 核心概念与实战
Vuex 是 Vue.js 应用程序的状态管理模式和库。它采用集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化。本教程将深入讲解 Vuex 的核心概念,如 State、Getter、Mutation 和 Action,并通过实战案例帮助开发者掌握在项目中有效使用 Vuex 的技巧。
|
29天前
|
存储 前端开发
在React框架中,如何使用对象来管理组件的状态
在React中,组件状态通过`state`对象管理,利用`setState`方法更新状态。状态变化触发组件重新渲染,实现UI动态更新。对象结构清晰,便于复杂状态管理。
|
2月前
|
存储 JavaScript 前端开发
Redux 状态管理入门
本文介绍了 Redux,一个广泛使用的 JavaScript 状态管理库,重点讲解了其核心概念(如 Store、Action、Reducer 等)、基本使用方法、常见问题及解决策略,并通过代码示例详细说明了如何在 React 应用中集成和使用 Redux。
84 1
|
2月前
|
前端开发 开发者 UED
你真的了解 Electron 的自动更新吗?揭秘AppUpdater 类的内部工作原理
本文由前端徐徐首发,深入探讨了 Electron 的自动更新工作原理,特别是 `electron-builder` 中 `AppUpdater` 类的源码分析,涵盖配置更新源、检查更新、下载更新、安装更新及事件通知等核心功能,帮助开发者更好地理解和使用 Electron 的自动更新机制。
153 0
你真的了解 Electron 的自动更新吗?揭秘AppUpdater 类的内部工作原理
|
4月前
|
前端开发 JavaScript
React的生命周期演示-旧(11)
【8月更文挑战第15天】React的生命周期演示-旧(11)
35 3
|
7月前
|
存储 JavaScript API
在Vue中,如何实现状态的共享?
在Vue中,如何实现状态的共享?
165 41
|
7月前
|
缓存 JavaScript 前端开发
Vue状态管理:请解释Vue中的异步组件加载是如何工作的?
Vue的异步组件通过`Vue.component()`实现,它接受组件配置、名称和回调函数。回调可返回Promise或IIFE以按需加载组件定义,提高性能。
32 0
|
7月前
|
JavaScript 前端开发
【干货分享】轻松入门 Redux:解密状态管理的神奇世界
【干货分享】轻松入门 Redux:解密状态管理的神奇世界
|
7月前
|
资源调度 JavaScript
如何使用 Vuex 管理应用的状态?
如何使用 Vuex 管理应用的状态?
33 3