> 文章列表 > 【三十天精通 Vue 3】 第十天 Vue 状态管理详解

【三十天精通 Vue 3】 第十天 Vue 状态管理详解

【三十天精通 Vue 3】 第十天 Vue 状态管理详解

请添加图片描述

✅创作者:陈书予
🎉个人主页:陈书予的个人主页
🍁陈书予的个人社区,欢迎你的加入: 陈书予的社区
🌟专栏地址: 三十天精通 Vue 3

文章目录

    • 引言
    • 一、Vue 3 状态管理概述
      • 1.1 状态管理的概念
      • 1.2 状态管理的作用
      • 1.3 Vue 3 中的状态管理方式
    • 二、Vue 3 响应式原理
      • 2.1 响应式原理概述
      • 2.2 Vue 3 中的响应式系统
      • 2.3 Vue 3 中的响应式 API
    • 三、Vue 3 中的数据流
      • 3.1 单向数据流的概念
      • 3.2 Vue 3 中的单向数据流
      • 3.3 Vue 3 中的双向数据绑定
    • 四、Vue 3 中的状态管理工具
      • 4.1 Vuex 4 的概述
      • 4.2 Vuex 4 的核心概念
        • 4.2.1 State
        • 4.2.2 Getters
        • 4.2.3 Mutations
        • 4.2.4 Actions
        • 4.2.5 Modules
      • 4.3 Vuex 4 的使用
        • 4.3.1 安装 Vuex
        • 4.3.2 创建 Store
        • 4.3.3 在 Vue 组件中使用 Vuex
    • 五、Vue 3 中的其他状态管理工具
      • 5.1 Pinia 的概述
      • 5.2 Pinia 的核心概念
        • 5.2.1 State
        • 5.2.2 Getters
        • 5.2.3 Mutations
        • 5.2.4 Actions
      • 5.3 Pinia 的使用
        • 5.3.1 安装 Pinia
        • 5.3.2 创建 Store
        • 5.3.3 在 Vue 组件中使用 Pinia
    • 六、Vue 3 中的状态管理最佳实践
      • 6.1 状态管理的最佳实践
      • 6.2 在 Vue 3 中的状态管理最佳实践
        • 6.2.1 在使用 Vuex 4 时的最佳实践
        • 6.2.2 在使用 Pinia 时的最佳实践
    • 七、Vue 3 状态管理常见问题及解决方案
      • 7.1 如何处理异步操作
      • 7.2 如何处理多层级嵌套的数据

引言

Vue 3 是一款流行的 JavaScript 前端框架,其拥有强大的响应式系统,易于学习和使用的状态管理工具,使得开发者可以轻松地管理组件状态和应用状态。今天,我们将深入探讨 Vue 3 中的状态管理机制,包括状态管理的概念、作用、响应式原理、数据流、状态管理工具以及最佳实践等方面。

一、Vue 3 状态管理概述

1.1 状态管理的概念

在 Web 应用中,状态是指组件和应用中的数据。例如,表单中的输入、复选框的状态、弹窗的开关状态、应用中的数据等都是状态。状态管理就是管理这些数据的过程。

在 Vue 3 中,组件和应用状态都可以用 JavaScript 对象表示。这些对象通常被称为状态对象。

1.2 状态管理的作用

状态管理是开发大型应用程序的关键功能之一。它可以帮助应用程序在不同的状态之间切换,并确保应用程序的数据始终处于最新状态。此外,状态管理还可以帮助开发人员更轻松地管理应用程序中的复杂数据结构。

1.3 Vue 3 中的状态管理方式

Vue 3 提供了两种状态管理方式:组件级别的状态管理和全局状态管理。

  • 组件级别的状态管理:

Vue 3 中的组件可以拥有自己的状态,并在组件内部进行状态的管理。这种方式适用于小型应用程序或组件之间的状态共享。

  • 全局状态管理:

Vue 3 中的应用程序可以使用全局状态管理工具来管理应用程序级别的状态。这种方式适用于大型应用程序,可以帮助开发人员更轻松地管理复杂的应用程序数据结构。

二、Vue 3 响应式原理

2.1 响应式原理概述

响应式原理是指当应用程序的状态发生变化时,应用程序的响应式数据也会发生变化。在 Vue 3 中,响应式系统是用于管理应用程序状态的核心功能。它由一个响应式数组和一组响应式函数组成,用于管理应用程序的状态。当响应式数组中的值发生变化时,响应式函数会被调用,并更新状态。

2.2 Vue 3 中的响应式系统

在 Vue 3 中,响应式系统是由一个响应式数组和一组响应式函数组成的。响应式数组中存储了应用程序状态的所有值,而响应式函数用于更新状态。当响应式数组中的值发生变化时,响应式函数会被调用,并更新状态。在 Vue 3 中,响应式系统提供了一些常用的指令,如 v-model、watch、computed 等,用于管理组件的状态。

2.3 Vue 3 中的响应式 API

Vue 3 中的响应式 API 提供了一组函数,用于管理应用程序的状态。这些函数包括:

  • 创建响应式数组:可以使用 new Vue () 函数创建响应式数组。
  • 更新状态:可以使用 push、pop、splice 等操作来更新响应式数组中的值。
  • 监听状态变化:可以使用 v-on 指令来监听状态的变化。

下面是一个使用 Vue 3 中的响应式系统管理状态的示例:

new Vue({  el: '#app',  data: {  message: 'Hello Vue!',  },  methods: {  reverseMessage() {  this.message = this.message.split('').reverse().join('')  },  },  
})  

在这个示例中,我们创建了一个 Vue 实例,并定义了一个数据对象 message,它包含一个字符串值。我们还定义了一个方法 reverseMessage,它用于将 message 对象中的字符串值进行反转。最后,我们使用 v-on 指令监听 message 对象的变化,并在必要时更新 message 对象的值。

三、Vue 3 中的数据流

3.1 单向数据流的概念

在 Vue 3 中,数据流是应用程序中数据从组件到父组件的传递过程。在 Vue 2 中,由于组件可以拥有自己的状态,因此组件之间的数据传递需要通过 props 进行传递。而在 Vue 3 中,组件可以拥有自己的响应式数据,因此组件之间的数据传递可以通过响应式数据进行传递,即单向数据流。

3.2 Vue 3 中的单向数据流

在 Vue 3 中,应用程序的数据流是单向的,即数据从组件流向父组件,而不能倒流。这是因为 Vue 3 中的组件可以拥有自己的响应式数据,并在组件内部进行状态管理。当组件需要向父组件传递数据时,可以通过 $emit 指令触发事件并将数据传递给父组件。父组件可以通过 $on 指令监听事件,并在事件触发时更新自身的状态。

3.3 Vue 3 中的双向数据绑定

在 Vue 3 中,双向数据绑定是 Vue 3 中的新特性之一,可以帮助开发人员更轻松地管理应用程序的状态。它允许组件之间的数据共享,并使组件的值与父组件的变量相互更新。在 Vue 3 中,双向数据绑定可以通过 v-model 指令实现。

下面是一个使用双向数据绑定的示例:

<template>    <div>    <input type="text" v-model="inputValue" />    <p>Input value: {{ inputValue }}</p>    </div>    
</template>  <script>    
export default {    data() {    return {    inputValue: ''    }    },    methods: {    changeInput() {    this.inputValue = 'Changed!'    }    }    
}    
</script>    

在这个示例中,我们定义了一个名为 App 的 Vue 实例。我们还定义了一个名为 inputValue 的数据对象,它包含一个字符串值。我们还定义了一个名为 changeInput 的方法,它用于更新 inputValue 对象的值。最后,我们使用 v-model 指令将 inputValue 对象与一个文本输入框进行绑定,并在文本输入框中显示 inputValue 对象的值。当用户更改文本输入框中的值时,inputValue 对象的值也会自动更新。

四、Vue 3 中的状态管理工具

4.1 Vuex 4 的概述

Vuex 4 的核心思想是单向数据流。这意味着应用程序的状态仅能通过定义的 actions 和 mutations 来修改,而不能直接修改。

Vuex 4 中的核心概念有 state、getters、mutations、actions 和 modules。接下来我们将分别介绍这些概念。

4.2 Vuex 4 的核心概念

4.2.1 State

在 Vuex 4 中,state 是应用程序中的核心数据。它类似于组件中的 data 对象,但是它是全局共享的。state 对象应该是响应式的,这样当 state 发生变化时,所有引用该对象的地方都将自动更新。

下面是一个示例 state 对象:

const state = {count: 0
}

4.2.2 Getters

在 Vuex 4 中,getters 可以用于从 state 中派生出一些状态。类似于 Vue 组件中的计算属性,getters 计算出的值会被缓存,只有当它的依赖项发生变化时才会重新计算。

下面是一个示例 getter:

const getters = {doubledCount(state) {return state.count * 2}
}

4.2.3 Mutations

在 Vuex 4 中,mutations 是用于修改 state 的方法。每个 mutation 都有一个字符串类型的事件类型和一个回调函数,这个回调函数接收当前的 state 作为第一个参数,以及一个可选的负载作为第二个参数。

所有的 mutations 都应该是同步的。这是因为如果一个 mutation 是异步的,那么当这个 mutation 发生时,无法保证 state 已经被更新。如果需要异步操作,可以使用 actions。

下面是一个示例 mutation:

const mutations = {increment(state, payload) {state.count += payload.amount}
}

4.2.4 Actions

在 Vuex 4 中,actions 用于处理异步操作和提交 mutations。每个 action 都有一个字符串类型的事件类型和一个回调函数,这个回调函数接收一个 context 对象作为参数,这个对象包含了 state、getters、commit、dispatch 等属性。

下面是一个示例 action:

const actions = {incrementAsync({ commit }, payload) {setTimeout(() => {commit('increment', payload)}, 1000)}
}

4.2.5 Modules

在 Vuex 4 中,可以将 store 分割成模块,每个模块拥有自己的 state、getters、mutations 和 actions。这样可以方便地对大型应用进行状态管理,也可以将不同模块的状态隔离开来,避免命名冲突。

下面是一个示例模块:

const moduleA = {state: { count: 0 },mutations: { increment(state) { state.count++ } },actions: { incrementAsync({ commit }) { setTimeout(() => commit('increment'), 1000) } },getters: { doubleCount(state) { return state.count * 2 } }
}const store = createStore({ modules: { moduleA } })

在上面的示例中,我们定义了一个名为 moduleA 的模块,它拥有自己的 state、mutations、actions 和 getters。我们将这个模块添加到 store 中,通过 store.state.moduleA 访问它的 state,在组件中使用 mapStatemapGettersmapMutationsmapActions 辅助函数来简化代码。

4.3 Vuex 4 的使用

4.3.1 安装 Vuex

在使用 Vuex 4 之前,需要先安装它。可以通过 npm 或 yarn 来安装 Vuex。

使用 npm 安装:

npm install vuex

使用 yarn 安装:

yarn add vuex

4.3.2 创建 Store

在使用 Vuex 时,需要创建一个 Vuex store 对象来管理应用中的状态。可以通过创建一个 store.js 文件来定义 Vuex store 对象,并在 main.js 中引入并注册到 Vue 实例中。

以下是一个示例 store.js 文件:

import { createStore } from 'vuex'const store = createStore({state: {count: 0},mutations: {increment(state) {state.count++},decrement(state) {state.count--}},actions: {incrementAsync({ commit }) {setTimeout(() => {commit('increment')}, 1000)}},getters: {getCount(state) {return state.count}}
})export default store

在上面的代码中,通过 createStore 函数创建一个 Vuex store 对象,并定义了 statemutationsactionsgetters 四个属性来管理状态、变更状态、处理异步操作和获取状态。

4.3.3 在 Vue 组件中使用 Vuex

在 Vue 组件中使用 Vuex,需要通过 mapStatemapMutationsmapActionsmapGetters 这四个辅助函数将 Vuex store 对象中的状态、变更、操作和计算属性映射到组件的属性和方法中。

以下是一个示例 Vue 组件:

<template><div><p>Count: {{ count }}</p><button @click="increment">Increment</button><button @click="decrement">Decrement</button><button @click="incrementAsync">Increment Async</button></div>
</template><script>
import { mapState, mapMutations, mapActions, mapGetters } from 'vuex'export default {computed: {...mapState(['count']),...mapGetters(['getCount'])},methods: {...mapMutations(['increment', 'decrement']),...mapActions(['incrementAsync'])}
}
</script>

在上面的代码中,通过 mapStatemapMutationsmapActionsmapGetters 这四个辅助函数将 Vuex store 对象中的状态、变更、操作和计算属性映射到组件的属性和方法中,从而可以方便地在组件中使用这些状态、变更、操作和计算属性。

五、Vue 3 中的其他状态管理工具

5.1 Pinia 的概述

Pinia 是一个由 Eduardo San Martin Morote 创建的新一代状态管理库,它是专门为 Vue 3 设计的。它的灵感来自于 Vuex,但它使用了 Vue 3 的响应式系统和强类型特性。Pinia 的目标是提供一个轻量、简单、可组合的状态管理库,使得在 Vue 3 应用中的状态管理变得更加容易。

与 Vuex 不同,Pinia 不提供中央 store,而是使用了基于 class 的 API 和 Vue 3 的组合 API,使得状态的管理和组合变得更加容易。Pinia 通过直接暴露出原始的响应式数据来提高性能,同时它还可以与 TypeScript 无缝集成。

5.2 Pinia 的核心概念

5.2.1 State

在 Pinia 中,state 是数据的存储区域,它是一个简单的对象,可以存储应用程序的任何数据。State 对象中的数据可以通过 refreactive 这两个 Vue 3 提供的函数来进行响应式处理。

下面是一个简单的 state 对象:

import { reactive } from 'vue'
export const useCounter = () => {const state = reactive({count: 0})return state
}

5.2.2 Getters

在 Pinia 中,getters 用于获取和计算 state 中的数据,它们类似于 Vuex 中的 getters。getters 可以接收 state 和其他 getters 作为参数,返回计算后的值。与 state 一样,getters 也是通过 refreactive 进行响应式处理的。

下面是一个简单的 getter:

import { computed } from 'vue'
import { useCounter } from './useCounter'export const useCounterGetters = () => {const state = useCounter()const doubleCount = computed(() => state.count * 2)return {doubleCount}
}

5.2.3 Mutations

在 Pinia 中,mutations 用于更改 state 中的数据,它们类似于 Vuex 中的 mutations。mutations 接收一个 state 对象作为参数,以及一个 payload 用于传递更改的数据。与 Vuex 不同的是,在 Pinia 中,mutations 是同步执行的,它们不能进行异步操作。

下面是一个简单的 mutation:

import { useCounter } from './useCounter'export const useCounterMutations = () => {const state = useCounter()const increment = (payload) => {state.count += payload}return {increment}
}

5.2.4 Actions

在 Pinia 中,actions 用于处理异步操作和提交 mutations。每个 action 都是一个函数,可以接收一个 payload 参数,返回一个 Promise 对象,Promise 对象的 resolve 方法可以用来提交 mutations。

下面是一个示例 action:

import { defineStore } from 'pinia'export const useCounterStore = defineStore('counter', {state: () => ({count: 0}),actions: {async incrementAsync(payload) {return new Promise(resolve => {setTimeout(() => {this.increment(payload)resolve()}, 1000)})}},mutations: {increment(state, payload) {state.count += payload}},getters: {doubleCount(state) {return state.count * 2}}
})

这个 action 使用了 setTimeout 模拟异步操作,当异步操作完成后,使用 resolve 方法提交一个 increment 的 mutation。

注意,在 Pinia 中,可以直接使用 this 访问 store 对象的属性和方法,因为每个 store 对象都是通过 defineStore 工厂函数创建的。

5.3 Pinia 的使用

5.3.1 安装 Pinia

要使用 Pinia,首先需要安装它。可以通过 npm 或 yarn 进行安装,如下所示:

使用 npm 安装:

npm install pinia

使用 yarn 安装:

yarn add pinia

5.3.2 创建 Store

在使用 Pinia 之前,需要先创建一个 store 对象。与 Vuex 不同,Pinia 没有全局的 store 对象,而是通过创建多个独立的 store 实例来进行状态管理。

创建 store 对象可以使用 defineStore 函数,该函数接受一个 store 配置对象作为参数。store 配置对象必须至少包含一个 state 属性,表示该 store 的状态。其他可选属性包括 actionsgettersmutations,与 Vuex 中的类似。下面是一个示例:

import { defineStore } from 'pinia'export const useCounterStore = defineStore('counter', {state: () => ({count: 0}),actions: {increment() {this.count++}},getters: {doubleCount() {return this.count * 2}},mutations: {reset() {this.count = 0}}
})

在上面的示例中,defineStore 函数创建了一个名为 useCounterStore 的 store 对象。该 store 包含一个 state 属性,表示该 store 的状态,初始值为 { count: 0 }。此外,该 store 包含三个方法,分别是 incrementdoubleCountreset,分别对应 actions、getters 和 mutations。

5.3.3 在 Vue 组件中使用 Pinia

与 Vuex 类似,可以在 Vue 组件中使用 useStore 函数来访问 store 对象。与 Vuex 不同的是,需要将 store 对象作为参数传递给 useStore 函数,如下所示:

<template><div><p>Count: {{ count }}</p><p>Double Count: {{ doubleCount }}</p><button @click="increment">Increment</button><button @click="reset">Reset</button></div>
</template><script>
import { defineComponent } from 'vue'
import { useStore } from 'pinia'
import { useCounterStore } from '@/stores/counter'export default defineComponent({setup() {const store = useStore(useCounterStore)return {count: store.count,doubleCount: store.doubleCount,increment: store.increment,reset: store.reset}}
})
</script>

在上面的示例中,使用 useStore 函数从 useCounterStore 中获取 store 对象,然后将 store 对象中的 countdoubleCountincrementreset 方法分别绑定到组件的 countdoubleCountincrementreset 变量上。这样,就可以在组件中访问 Pinia store 的状态和方法了。

六、Vue 3 中的状态管理最佳实践

6.1 状态管理的最佳实践

状态管理是 Vue 应用程序中非常重要的一部分。在实现状态管理时,有一些最佳实践可以帮助您编写更好的代码并避免一些常见的问题。

以下是一些状态管理的最佳实践:

  1. 单一数据源:在应用程序中只使用一个数据源(store),这样可以简化代码,并确保您的数据在整个应用程序中保持一致。
  2. 只能通过 mutations 修改状态:mutations 是唯一可以修改状态的方法。不要直接修改状态,因为这样可能会导致不可预测的行为。
  3. 异步操作应该在 actions 中处理:actions 用于处理异步操作和提交 mutations。在 actions 中,您可以执行异步操作并提交 mutations 以更新状态。
  4. Getters 应该用于派生状态:Getters 应该用于从状态中派生值,而不是直接访问状态。这样可以使代码更易于理解和维护。
  5. 分割 Store:如果 Store 变得太大或太复杂,可以将其分割成模块。每个模块都有自己的 state、getters、mutations 和 actions。
  6. 在开发过程中启用严格模式:在开发过程中,启用严格模式可以帮助您捕获不合法的状态修改。

6.2 在 Vue 3 中的状态管理最佳实践

6.2.1 在使用 Vuex 4 时的最佳实践

  1. 对 state 进行模块化组织:将 state 按照业务功能拆分成多个模块,便于管理和维护。
  2. 使用辅助函数简化代码:在 getters 和 mutations 中使用辅助函数(如 mapGetters、mapMutations),可以使代码更加简洁易读。
  3. 处理异步操作:异步操作应该在 actions 中进行处理,可以使用 async/await 或者 Promise 等方式,以及结合 try/catch 来处理异步操作的错误。
  4. 使用插件增强 Vuex 功能:可以使用插件来增强 Vuex 的功能,比如 vuex-persistedstate 可以将 Vuex 的 state 持久化到本地存储中。

6.2.2 在使用 Pinia 时的最佳实践

  1. 对 state 进行模块化组织:和 Vuex 一样,将 state 按照业务功能拆分成多个模块,便于管理和维护。
  2. 使用辅助函数简化代码:和 Vuex 一样,可以在 getters 和 mutations 中使用辅助函数(如 mapGetters、mapMutations),可以使代码更加简洁易读。
  3. 利用 TypeScript 的类型检查:在使用 TypeScript 时,可以利用类型检查来避免一些错误,比如在定义 state 类型时,可以使用 Partial 或者 Required 等类型来定义初始值或者必填字段等。
  4. 使用插件增强 Pinia 功能:和 Vuex 一样,可以使用插件来增强 Pinia 的功能,比如 pinia-plugin-persist 可以将 Pinia 的 state 持久化到本地存储中。

七、Vue 3 状态管理常见问题及解决方案

7.1 如何处理异步操作

在处理异步操作时,可以使用 actions 或者 effects 来进行处理。在 Vuex 4 中,actions 用于处理异步操作,可以在 action 中进行异步操作,然后通过 commit 触发 mutation 来修改 state。在 Pinia 中,可以使用 effects 来处理异步操作,它可以直接返回 Promise 对象,Pinia 会等待 Promise 完成之后再进行状态更新。

例如,在 Vuex 4 中:

const actions = {async fetchUser({ commit }, userId) {const user = await getUser(userId)commit('SET_USER', user)}
}

在 Pinia 中:

import { defineStore } from 'pinia'export const useUserStore = defineStore({id: 'user',state: () => ({user: null}),actions: {async fetchUser(userId) {const user = await getUser(userId)this.SET_USER(user)}},getters: {isLoggedIn() {return Boolean(this.user)}},mutations: {SET_USER(user) {this.user = user}}
})

7.2 如何处理多层级嵌套的数据

在处理多层级嵌套的数据时,可以使用 Vuex 4 的 modules 或者 Pinia 的 submodules 来进行处理。通过将 store 拆分成多个模块或子模块,可以将不同的数据状态进行分类管理,同时也能够避免状态命名冲突的问题。

例如,在 Vuex 4 中:

const moduleA = {state: () => ({count: 0}),mutations: {increment(state) {state.count++}}
}const moduleB = {state: () => ({user: {name: 'John',age: 18,address: {city: 'New York',street: 'Broadway'}}}),mutations: {updateUserName(state, newName) {state.user.name = newName}}
}const store = createStore({modules: {a: moduleA,b: moduleB}
})

在 Pinia 中:

import { defineStore } from 'pinia'export const useUserStore = defineStore({id: 'user',state: () => ({name: 'John',age: 18,address: {city: 'New York',street: 'Broadway'}}),getters: {fullAddress() {return `${this.address.street}, ${this.address.city}`}},mutations: {updateName(newName) {this.name = newName}}
})export const useCounterStore = defineStore({id: 'counter',state: () => ({count: 0}),mutations: {increment() {this.count++}}
})