vue 数据管理 vuex全解加手写

242 阅读5分钟

大家好,距离上次写文章已经不知不觉过了一周了,因为忙着投简历和面试。星期一的面试也是我人生中的第一次面试,面试前颇感紧张,但是在面试的时候并没有想象中的紧张。

平均每天三场面试的生活多少会有点疲惫,看着同学以及过了字节一面,我觉得我也需要加把劲了,但是目前还是需要沉淀,今天主要跟大家分享的是vuex的一些用法(ps:面试常常被考到)

当然,手写vuex也是必不可少的。问就是被面试官问到过读过哪些vue的源码,这之前只看过hash路由的写法。

闲话不多说,开始今天的正文了

正文

随着应用程序规模的不断增大,组件间的通信和状态管理会变得越来越复杂。传统的vue组件之间的通信已经不够用了,在没有集中状态管理的情况下,组件可能需要通过多层嵌套的父组件传递 props 或者使用事件来与其他组件通信,这种方法在简单应用中可行,但在大型应用中会导致代码结构混乱,难以维护。所以vuex被设计出来了

vuex其实是一种状态管理模式,首先它使用createStore创建了一个单一状态树。 在 Vuex 中,statemutationsactionsgetters 构成了其核心组成部分,每个部分都有其特定的功能和使用场景:

  1. State

    • StateVuex 中存储应用程序状态的地方。它是一个普通的 JavaScript 对象,可以包含任意数量的属性。这些属性代表了应用程序的当前状态,例如用户登录状态、购物车中的商品列表等。State 的值是响应式的,意味着当 State 发生改变时,所有依赖于这些状态的组件会自动更新。
  2. Mutations

    • Mutations 是唯一可以改变 Vuex State 的方式。它们本质上是一些函数,每个函数都有一个字符串类型的事件类型作为标识。Mutations 必须是同步函数,这使得状态的变更变得可预测和容易调试。通过提交 Mutations,你可以更新 State,并且这些更新会被 Vuex 的响应式系统捕获,触发组件的重新渲染。
  3. Actions

    • Actions 类似于 Mutations,不同之处在于它们可以包含任意异步操作,如 API 调用。Actions 不直接改变 State,而是通过提交 Mutations 来触发状态的变更。Actions 提供了一种解耦的方式,使得业务逻辑不会直接与状态变更绑定,同时也方便了错误处理和测试。
  4. Getters

    • Getters 可以理解为 Vuex 中的计算属性。它们基于 State 返回派生状态,可以被组件用来获取经过加工的状态信息。Getters 是响应式的,也就是说当 State 发生变化时,Getters 的结果也会自动更新。

当数据的状态比较多的时候,vuex还可以支持模块化管理,每个模块都应由上面四个组成。

例如,当我需要打造一个购物车的状态数据时:

import { createStore, } from 'vuex'
// vuex pinia  复杂, 中央仓库的概念 store 单例 单一状态树
import cart from './modules/cart';
import products from './modules/products';

export default createStore({
    //全局状态
    state: {
        count: 0
    },
    modules: {
        cart,//购物车状态
        products//商品状态
    }
})

可以划分为购物车状态和商品状态,这两个状态中会有相应的状态数据和方法。

注意当我们使用actions时,需要使用dispacth()去触发,而在actions中需要使用commit提交mutations去修改状态。这种做法使得vuex在状态管理中更加严格可控。

当我们了解完vuex的工作原理之后,就可以开始手写一个简单的vuex了。

手写vuex

首先,我准备了一个仓库store/index.js:

import { createStore } from './gvuex.js'; 

const store = createStore({
    // 全局状态
    state() {
        return {
            count: 1
        }
    },
    getters: {
        double(state) {
            return state.count * 2
        }
    },
    mutations: {
        add(state) {
            state.count++
        }
    },
    actions: {
        asyncAdd({ commit }) {
            setTimeout(() => {
                commit('add')
            }, 1000)
        }
    }
}) 

export default store

我们需要在gvuex.js中打造一个自己的vuex

先定义一个createStore()去返回Store的实例对象

export const createStore = (options) => {
    return new Store(options)
}

接着,打造Store类,这里我使用了es6的class语法糖,在constructor中传入创建的仓库,然后进行了初始化

把它们设为私有属性

  • this.$options: 保存了传入的配置选项。
  • this._state: 使用 reactive 创建了一个响应式的对象,其中包含状态数据。状态数据由 options.state() 方法返回。
  • this._mutations: 保存了提交 mutations 的方法。
  • this._actions: 保存了调度 actions 的方法。

vuex中的getters其实就是计算属性

class Store {
    constructor(options) {
        this.$options = options // 保存
        // 私有属性
        this._state = reactive({
            data: options.state()
        })
        // 私有属性 commit dispatch 
        this._mutations = options.mutations
        this._actions = options.actions
        this.getters = {}

        Object.keys(options.getters).forEach(name => {
            const fn = options.getters[name] // 计算函数
            this.getters[name] = computed(() => fn(this.state))
        })
    }
}

然后我使用get 关键字让store.state里的数据设为只读

    get state() {
        return this._state.data
    }

接着,实现了commit 和dispatch

    commit = (type, payload) => {
        const entry = this._mutations[type]
        entry && entry(this.state, payload)
    }

    dispatch(type, payload) {
        const entry = this._actions[type]
        entry && entry(this, payload)
    }

根据type找到actions和mutations里的函数并调用。

最后,使用install方法,把Store的实例对象提供给vue,并且使用provide注入将Store实例传给子组件


install (app) {
        // KEY, store对象
        app.provide(STORE_KEY, this)
    }

全局,让子组件可以调用store实例:

const STORE_KEY = '__store__'
export const useStore = () => {
    return inject(STORE_KEY)
}

注意还需要在入口文件中加载这个插件

main.js

import { createApp } from 'vue'
import './style.css'
import App from './App.vue'
import store from './store'

createApp(App)
    // install 方法, vue 和其他生态的接口
    .use(store)
    .mount('#app')

到这里一个vuex状态管理模式就完成了。

我们可以在App.vue 中去试验一下:

<template>
  <div>
    {{ count }}
  </div>
</template>

<script setup>
import { computed } from 'vue';
import { useStore } from './store/gvuex'

const store = useStore()
const count = computed(
  () => store.state.count
)
// store.state.count++
store.dispatch('asyncAdd')
</script>

<style lang="scss" scoped>

</style>

当然可以!这里为您准备一段结语:


结语

感谢您阅读本文!通过本文的介绍,我们探讨了 Vuex 在 Vue.js 应用程序中的重要性,以及如何通过手写一个简化的 Vuex 实现来加深对其工作原理的理解。希望这些内容能够帮助您更好地掌握 Vuex 的使用,并在未来的工作中发挥重要作用。