✅创作者:陈书予
个人主页:陈书予的个人主页
陈书予的个人社区,欢迎你的加入: 陈书予的社区
专栏地址: 三十天精通 Vue 3
Vue 3 是一款流行的 JavaScript 前端框架,其拥有强大的响应式系统,易于学习和使用的状态管理工具,使得开发者可以轻松地管理组件状态和应用状态。今天,我们将深入探讨 Vue 3 中的状态管理机制,包括状态管理的概念、作用、响应式原理、数据流、状态管理工具以及最佳实践等方面。
在 Web 应用中,状态是指组件和应用中的数据。例如,表单中的输入、复选框的状态、弹窗的开关状态、应用中的数据等都是状态。状态管理就是管理这些数据的过程。
在 Vue 3 中,组件和应用状态都可以用 JavaScript 对象表示。这些对象通常被称为状态对象。
状态管理是开发大型应用程序的关键功能之一。它可以帮助应用程序在不同的状态之间切换,并确保应用程序的数据始终处于最新状态。此外,状态管理还可以帮助开发人员更轻松地管理应用程序中的复杂数据结构。
Vue 3 提供了两种状态管理方式:组件级别的状态管理和全局状态管理。
Vue 3 中的组件可以拥有自己的状态,并在组件内部进行状态的管理。这种方式适用于小型应用程序或组件之间的状态共享。
Vue 3 中的应用程序可以使用全局状态管理工具来管理应用程序级别的状态。这种方式适用于大型应用程序,可以帮助开发人员更轻松地管理复杂的应用程序数据结构。
响应式原理是指当应用程序的状态发生变化时,应用程序的响应式数据也会发生变化。在 Vue 3 中,响应式系统是用于管理应用程序状态的核心功能。它由一个响应式数组和一组响应式函数组成,用于管理应用程序的状态。当响应式数组中的值发生变化时,响应式函数会被调用,并更新状态。
在 Vue 3 中,响应式系统是由一个响应式数组和一组响应式函数组成的。响应式数组中存储了应用程序状态的所有值,而响应式函数用于更新状态。当响应式数组中的值发生变化时,响应式函数会被调用,并更新状态。在 Vue 3 中,响应式系统提供了一些常用的指令,如 v-model、watch、computed 等,用于管理组件的状态。
Vue 3 中的响应式 API 提供了一组函数,用于管理应用程序的状态。这些函数包括:
下面是一个使用 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 中,数据流是应用程序中数据从组件到父组件的传递过程。在 Vue 2 中,由于组件可以拥有自己的状态,因此组件之间的数据传递需要通过 props 进行传递。而在 Vue 3 中,组件可以拥有自己的响应式数据,因此组件之间的数据传递可以通过响应式数据进行传递,即单向数据流。
在 Vue 3 中,应用程序的数据流是单向的,即数据从组件流向父组件,而不能倒流。这是因为 Vue 3 中的组件可以拥有自己的响应式数据,并在组件内部进行状态管理。当组件需要向父组件传递数据时,可以通过 $emit 指令触发事件并将数据传递给父组件。父组件可以通过 $on 指令监听事件,并在事件触发时更新自身的状态。
在 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 对象的值也会自动更新。
Vuex 4 的核心思想是单向数据流。这意味着应用程序的状态仅能通过定义的 actions 和 mutations 来修改,而不能直接修改。
Vuex 4 中的核心概念有 state、getters、mutations、actions 和 modules。接下来我们将分别介绍这些概念。
在 Vuex 4 中,state 是应用程序中的核心数据。它类似于组件中的 data 对象,但是它是全局共享的。state 对象应该是响应式的,这样当 state 发生变化时,所有引用该对象的地方都将自动更新。
下面是一个示例 state 对象:
const state = {
count: 0
}
在 Vuex 4 中,getters 可以用于从 state 中派生出一些状态。类似于 Vue 组件中的计算属性,getters 计算出的值会被缓存,只有当它的依赖项发生变化时才会重新计算。
下面是一个示例 getter:
const getters = {
doubledCount(state) {
return state.count * 2
}
}
在 Vuex 4 中,mutations 是用于修改 state 的方法。每个 mutation 都有一个字符串类型的事件类型和一个回调函数,这个回调函数接收当前的 state 作为第一个参数,以及一个可选的负载作为第二个参数。
所有的 mutations 都应该是同步的。这是因为如果一个 mutation 是异步的,那么当这个 mutation 发生时,无法保证 state 已经被更新。如果需要异步操作,可以使用 actions。
下面是一个示例 mutation:
const mutations = {
increment(state, payload) {
state.count += payload.amount
}
}
在 Vuex 4 中,actions 用于处理异步操作和提交 mutations。每个 action 都有一个字符串类型的事件类型和一个回调函数,这个回调函数接收一个 context 对象作为参数,这个对象包含了 state、getters、commit、dispatch 等属性。
下面是一个示例 action:
const actions = {
incrementAsync({ commit }, payload) {
setTimeout(() => {
commit('increment', payload)
}, 1000)
}
}
在 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,在组件中使用 mapState
、mapGetters
、mapMutations
和 mapActions
辅助函数来简化代码。
在使用 Vuex 4 之前,需要先安装它。可以通过 npm 或 yarn 来安装 Vuex。
使用 npm 安装:
npm install vuex
使用 yarn 安装:
yarn add vuex
在使用 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 对象,并定义了 state
、mutations
、actions
和 getters
四个属性来管理状态、变更状态、处理异步操作和获取状态。
在 Vue 组件中使用 Vuex,需要通过 mapState
、mapMutations
、mapActions
和 mapGetters
这四个辅助函数将 Vuex store 对象中的状态、变更、操作和计算属性映射到组件的属性和方法中。
以下是一个示例 Vue 组件:
<template>
<div>
<p>Count: {{ count }}p>
<button @click="increment">Incrementbutton>
<button @click="decrement">Decrementbutton>
<button @click="incrementAsync">Increment Asyncbutton>
div>
template>
<script>
import { mapState, mapMutations, mapActions, mapGetters } from 'vuex'
export default {
computed: {
...mapState(['count']),
...mapGetters(['getCount'])
},
methods: {
...mapMutations(['increment', 'decrement']),
...mapActions(['incrementAsync'])
}
}
script>
在上面的代码中,通过 mapState
、mapMutations
、mapActions
和 mapGetters
这四个辅助函数将 Vuex store 对象中的状态、变更、操作和计算属性映射到组件的属性和方法中,从而可以方便地在组件中使用这些状态、变更、操作和计算属性。
Pinia 是一个由 Eduardo San Martin Morote 创建的新一代状态管理库,它是专门为 Vue 3 设计的。它的灵感来自于 Vuex,但它使用了 Vue 3 的响应式系统和强类型特性。Pinia 的目标是提供一个轻量、简单、可组合的状态管理库,使得在 Vue 3 应用中的状态管理变得更加容易。
与 Vuex 不同,Pinia 不提供中央 store,而是使用了基于 class 的 API 和 Vue 3 的组合 API,使得状态的管理和组合变得更加容易。Pinia 通过直接暴露出原始的响应式数据来提高性能,同时它还可以与 TypeScript 无缝集成。
在 Pinia 中,state 是数据的存储区域,它是一个简单的对象,可以存储应用程序的任何数据。State 对象中的数据可以通过 ref
和 reactive
这两个 Vue 3 提供的函数来进行响应式处理。
下面是一个简单的 state 对象:
import { reactive } from 'vue'
export const useCounter = () => {
const state = reactive({
count: 0
})
return state
}
在 Pinia 中,getters 用于获取和计算 state 中的数据,它们类似于 Vuex 中的 getters。getters 可以接收 state 和其他 getters 作为参数,返回计算后的值。与 state 一样,getters 也是通过 ref
和 reactive
进行响应式处理的。
下面是一个简单的 getter:
import { computed } from 'vue'
import { useCounter } from './useCounter'
export const useCounterGetters = () => {
const state = useCounter()
const doubleCount = computed(() => state.count * 2)
return {
doubleCount
}
}
在 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
}
}
在 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
工厂函数创建的。
要使用 Pinia,首先需要安装它。可以通过 npm 或 yarn 进行安装,如下所示:
使用 npm 安装:
npm install pinia
使用 yarn 安装:
yarn add pinia
在使用 Pinia 之前,需要先创建一个 store 对象。与 Vuex 不同,Pinia 没有全局的 store 对象,而是通过创建多个独立的 store 实例来进行状态管理。
创建 store 对象可以使用 defineStore
函数,该函数接受一个 store 配置对象作为参数。store 配置对象必须至少包含一个 state
属性,表示该 store 的状态。其他可选属性包括 actions
、getters
和 mutations
,与 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 包含三个方法,分别是 increment
、doubleCount
和 reset
,分别对应 actions、getters 和 mutations。
与 Vuex 类似,可以在 Vue 组件中使用 useStore
函数来访问 store 对象。与 Vuex 不同的是,需要将 store 对象作为参数传递给 useStore
函数,如下所示:
<template>
<div>
<p>Count: {{ count }}p>
<p>Double Count: {{ doubleCount }}p>
<button @click="increment">Incrementbutton>
<button @click="reset">Resetbutton>
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 对象中的 count
、doubleCount
、increment
和 reset
方法分别绑定到组件的 count
、doubleCount
、increment
和 reset
变量上。这样,就可以在组件中访问 Pinia store 的状态和方法了。
状态管理是 Vue 应用程序中非常重要的一部分。在实现状态管理时,有一些最佳实践可以帮助您编写更好的代码并避免一些常见的问题。
以下是一些状态管理的最佳实践:
在处理异步操作时,可以使用 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
}
}
})
在处理多层级嵌套的数据时,可以使用 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++
}
}
})