前端 Vue 专栏 10:Vuex、Pinia 与前端状态管理
前言
前面的文章已经介绍了组件通信和 Vue Router。随着页面增多,我们还会遇到一个问题:
多个组件和页面需要使用同一份数据时,这份数据应该放在哪里?
例如登录成功后,下面这些地方都需要读取当前用户:
顶部导航栏 → 显示用户名
个人中心 → 显示用户资料
权限菜单 → 判断用户角色
订单页面 → 判断是否登录
如果每个组件都各自请求和保存用户信息,容易产生重复请求和数据不一致;如果一直通过 props 逐层传递,组件关系又会变得很复杂。
这时可以使用全局状态管理。
Vue 项目中最常见的两种状态管理方案是:
Vuex → Vue 早期项目和存量项目中常见
Pinia → 当前 Vue 新项目通常优先采用
这一篇将围绕普通前端开发和秋招高频问题展开:
什么是全局状态;
什么时候需要状态管理;
Vuex 的 state、getters、mutations、actions;
Vuex 为什么区分 mutation 和 action;
Vuex modules 有什么用;
Pinia 的 state、getters、actions;
storeToRefs 为什么能够保持响应性;
Vuex 和 Pinia 有什么区别;
实际项目应该怎样选择。
一、什么是状态管理
状态是会影响页面展示或业务行为的数据:
const user = ref(null)
const cartItems = ref([])
const theme = ref('light')
它们分别决定:
当前登录的是谁;
购物车有哪些商品;
页面使用浅色还是深色主题。
状态管理主要解决四个问题:
状态保存在哪里;
哪些组件可以读取;
通过什么方式修改;
状态变化后哪些页面需要更新。
Vuex 和 Pinia 都会把 store 中的 state 接入 Vue 响应式系统:
store 状态发生变化
↓
依赖它的 getter 重新计算
↓
使用它的组件响应更新
它们的主要区别不在于“能不能响应式”,而在于状态和操作的组织方式。
二、什么时候需要全局状态
适合放入全局 store 的数据通常具有以下特点:
多个无直接父子关系的组件需要使用;
切换路由后仍然需要保留;
需要在多个位置保持一致;
围绕它存在统一的业务操作。
常见例子:
当前用户和登录状态;
购物车;
全局主题;
权限信息;
需要跨页面保存的流程数据。
下面这些状态通常不需要放入全局 store:
当前组件的弹窗是否打开;
输入框正在输入的临时内容;
某个局部菜单是否展开;
只在当前页面使用的简单请求结果。
状态管理不是越多越好。可以按照下面的顺序判断:
单个组件使用 → ref / reactive
明确的父子通信 → props / emit
局部组件树共享 → provide / inject
多个页面全局共享 → Vuex / Pinia
第一部分:Vuex
三、Vuex 的整体结构
Vuex 是集中式状态管理库,使用一个 store 保存应用级状态。
它有五个核心概念:
| 概念 | 作用 |
|---|---|
state | 保存原始状态 |
getters | 根据 state 计算派生状态 |
mutations | 同步修改 state |
actions | 组织业务和异步操作 |
modules | 按业务拆分大型 store |
最重要的数据流是:
同步修改:
组件 → commit mutation → 修改 state → 页面更新
异步业务:
组件 → dispatch action → commit mutation → 修改 state → 页面更新
其中两个词需要先记住:
commit → 提交 mutation
dispatch → 派发 action
四、创建和注册 Vuex Store
Vue 3 项目通常使用 Vuex 4:
npm install vuex@4
创建 store:
// src/store/index.js
import { createStore } from 'vuex'
const store = createStore({
state() {
return {
count: 0
}
},
getters: {
doubleCount(state) {
return state.count * 2
}
},
mutations: {
increment(state) {
state.count++
}
},
actions: {
incrementAsync({ commit }) {
setTimeout(() => {
commit('increment')
}, 1000)
}
}
})
export default store
在入口文件中注册:
// src/main.js
import { createApp } from 'vue'
import App from './App.vue'
import store from './store'
const app = createApp(App)
app.use(store)
app.mount('#app')
注册后,应用中的组件会使用同一个 Vuex store。
五、Vuex state 和 getters
1. state
state 用来保存原始状态:
state() {
return {
count: 0,
user: null,
cartItems: []
}
}
读取方式:
store.state.count
store.state.user
store.state.cartItems
Vuex state 是响应式的。state 变化后,使用它的组件会更新。
2. getters
getters 类似 store 中的 computed:
getters: {
doubleCount(state) {
return state.count * 2
},
isLoggedIn(state) {
return Boolean(state.user)
}
}
读取方式:
store.getters.doubleCount
store.getters.isLoggedIn
普通 getter 作为属性读取,不需要写成函数调用:
store.getters.doubleCount
而不是:
store.getters.doubleCount()
如果一个值能够完全由 state 计算出来,就优先考虑 getter,避免保存两份可能不同步的状态。
六、Vuex mutations:同步修改状态
在 Vuex 的标准数据流中,mutation 负责真正修改 state:
mutations: {
increment(state) {
state.count++
}
}
通过 commit 提交:
store.commit('increment')
执行过程:
commit('increment')
↓
找到 increment mutation
↓
执行 state.count++
↓
组件更新
mutation 接收参数
mutations: {
incrementBy(state, amount) {
state.count += amount
}
}
提交时传递 payload:
store.commit('incrementBy', 5)
也可以传递对象:
mutations: {
setUser(state, user) {
state.user = user
}
}
store.commit('setUser', {
id: 1001,
name: '小明'
})
mutation 为什么要求同步
Vuex 希望每次状态变化都能够被开发工具准确记录:
mutation 执行前的 state
↓
同步执行 mutation
↓
mutation 执行后的 state
如果在 mutation 中放入异步请求,状态具体在什么时候改变就难以对应到本次 mutation。
因此不要这样写:
mutations: {
async fetchUser(state) {
const response = await fetch('/api/user')
state.user = await response.json()
}
}
异步逻辑应该放在 action 中。
七、Vuex actions:处理业务和异步操作
action 可以执行同步或异步业务,但按照 Vuex 的设计,它通过提交 mutation 修改状态。
actions: {
incrementAsync(context) {
setTimeout(() => {
context.commit('increment')
}, 1000)
}
}
action 的第一个参数是 context:
context.state → 读取 state
context.getters → 读取 getters
context.commit → 提交 mutation
context.dispatch → 调用其他 action
通常直接解构需要的内容:
actions: {
incrementAsync({ commit }) {
setTimeout(() => {
commit('increment')
}, 1000)
}
}
通过 dispatch 调用:
store.dispatch('incrementAsync')
一个真实请求例子
const store = createStore({
state() {
return {
user: null,
loading: false,
error: null
}
},
mutations: {
setLoading(state, loading) {
state.loading = loading
},
setUser(state, user) {
state.user = user
},
setError(state, error) {
state.error = error
}
},
actions: {
async fetchUser({ commit }, userId) {
commit('setLoading', true)
commit('setError', null)
try {
const response = await fetch(
`/api/users/${userId}`
)
if (!response.ok) {
throw new Error('获取用户失败')
}
const user = await response.json()
commit('setUser', user)
return user
} catch (error) {
commit('setError', error.message)
throw error
} finally {
commit('setLoading', false)
}
}
}
})
组件调用:
await store.dispatch('fetchUser', 1001)
流程是:
dispatch fetchUser
↓
action 发起请求
↓
action commit setUser
↓
mutation 修改 state.user
↓
组件响应更新
同步操作必须经过 action 吗
不一定。
简单同步修改可以直接:
store.commit('increment')
异步请求或复杂业务再使用:
store.dispatch('fetchUser')
不要为了形式统一,给每一个简单 mutation 都套一层没有业务逻辑的 action。
八、组件如何使用 Vuex
组合式 API 中使用 useStore():
<script setup>
import { computed } from 'vue'
import { useStore } from 'vuex'
const store = useStore()
const count = computed(
() => store.state.count
)
const doubleCount = computed(
() => store.getters.doubleCount
)
function increment() {
store.commit('increment')
}
function incrementAsync() {
store.dispatch('incrementAsync')
}
</script>
<template>
<p>数量:{{ count }}</p>
<p>两倍:{{ doubleCount }}</p>
<button @click="increment">
同步增加
</button>
<button @click="incrementAsync">
延迟增加
</button>
</template>
需要记住四种访问方式:
| 操作 | Vuex 写法 |
|---|---|
| 读取 state | store.state.count |
| 读取 getter | store.getters.doubleCount |
| 提交 mutation | store.commit('increment') |
| 派发 action | store.dispatch('incrementAsync') |
存量项目的选项式 API 中还经常出现:
mapState
mapGetters
mapMutations
mapActions
它们只是把 store 中的状态和方法映射到组件中。秋招需要能看懂,但不必把所有写法都背下来。
九、Vuex modules 有什么用
Vuex 使用单一 store。业务变多以后,根 store 可能非常庞大:
store
├── user
├── cart
├── product
├── order
└── theme
modules 用来按照业务拆分:
const userModule = {
namespaced: true,
state: () => ({
user: null
}),
getters: {
isLoggedIn(state) {
return Boolean(state.user)
}
},
mutations: {
setUser(state, user) {
state.user = user
}
},
actions: {
async fetchUser({ commit }) {
const response = await fetch('/api/user')
const user = await response.json()
commit('setUser', user)
}
}
}
注册模块:
const store = createStore({
modules: {
user: userModule
}
})
读取模块状态:
store.state.user.user
开启:
namespaced: true
以后,提交和派发时带上模块名:
store.commit('user/setUser', user)
store.dispatch('user/fetchUser')
可以这样理解:
user/setUser
│ └── mutation 名称
└─────── module 命名空间
对于普通前端开发,掌握模块拆分和 namespaced: true 就足够了,不需要深入动态注册和多层嵌套模块。
第二部分:Pinia
十、Pinia 的整体结构
Pinia 也是 Vue 的状态管理库,但它简化了 Vuex 的数据流。
Pinia 主要有三个概念:
| 概念 | 作用 |
|---|---|
state | 保存原始状态 |
getters | 计算派生状态 |
actions | 封装同步或异步业务,并可直接修改 state |
最重要的流程是:
组件调用 action
↓
action 执行业务并修改 state
↓
getters 重新计算
↓
组件响应更新
与 Vuex 相比,Pinia 没有单独的 mutations:
Vuex:action → commit mutation → state
Pinia:action → state
十一、创建和注册 Pinia
安装:
npm install pinia
注册:
// src/main.js
import { createApp } from 'vue'
import { createPinia } from 'pinia'
import App from './App.vue'
const app = createApp(App)
const pinia = createPinia()
app.use(pinia)
app.mount('#app')
Pinia 通常按照业务拆分多个 store:
src/stores/
├── user.js
├── cart.js
└── theme.js
每个 store 都有独立 id:
defineStore('user', {})
defineStore('cart', {})
defineStore('theme', {})
不同 store 的 id 不应该重复。
十二、定义 Pinia Option Store
// src/stores/counter.js
import { defineStore } from 'pinia'
export const useCounterStore = defineStore('counter', {
state: () => ({
count: 0
}),
getters: {
doubleCount: state => state.count * 2
},
actions: {
increment() {
this.count++
},
async incrementAsync() {
await new Promise(resolve => {
setTimeout(resolve, 1000)
})
this.count++
}
}
})
对应关系:
state:count
getter:doubleCount
action:increment、incrementAsync
组件中先调用:
const counterStore = useCounterStore()
得到 store 实例后,可以:
counterStore.count
counterStore.doubleCount
counterStore.increment()
counterStore.incrementAsync()
十三、Pinia state、getters 和 actions
1. state
Option Store 中直接声明普通初始值即可:
state: () => ({
count: 0,
user: null,
items: []
})
Pinia 会将它们纳入 Vue 响应式系统,不需要写成:
state: () => ({
count: ref(0)
})
Pinia 允许直接修改 state:
counterStore.count++
不过涉及请求、校验或多步修改的业务,通常封装为 action 更清晰:
cartStore.addItem(product)
2. getters
getters: {
totalCount: state => {
return state.items.reduce(
(sum, item) => sum + item.quantity,
0
)
}
}
像属性一样读取:
cartStore.totalCount
getters 适合保存由 state 推导出的计算规则,不适合执行网络请求和产生副作用。
3. actions
Pinia action 中同步和异步逻辑都可以写:
actions: {
increment() {
this.count++
},
async fetchUser() {
const response = await fetch('/api/user')
this.user = await response.json()
}
}
与 Vuex 不同,Pinia action 可以直接修改 state,不需要再 commit mutation。
Option Store 的 action 如果使用 this,不要使用箭头函数:
actions: {
// 错误:箭头函数没有自己的 this
increment: () => {
this.count++
}
}
应该写成普通方法:
actions: {
increment() {
this.count++
}
}
十四、组件如何使用 Pinia
<script setup>
import { useCounterStore } from '@/stores/counter'
const counterStore = useCounterStore()
</script>
<template>
<p>数量:{{ counterStore.count }}</p>
<p>两倍:{{ counterStore.doubleCount }}</p>
<button @click="counterStore.increment">
增加
</button>
</template>
直接使用:
counterStore.count
counterStore.doubleCount
会保持响应性。
但是不要直接解构 state 和 getter:
const {
count,
doubleCount
} = counterStore
这可能会取得解构时的普通值,丢失与 store 的响应式联系。
需要解构时使用 storeToRefs():
<script setup>
import { storeToRefs } from 'pinia'
import { useCounterStore } from '@/stores/counter'
const counterStore = useCounterStore()
const {
count,
doubleCount
} = storeToRefs(counterStore)
const {
increment,
incrementAsync
} = counterStore
</script>
规则是:
不解构 state/getters → 直接使用 store.xxx
解构 state/getters → 使用 storeToRefs(store)
解构 actions → 可以直接从 store 解构
actions 是已经由 Pinia 绑定到 store 的方法,本身不是需要响应式追踪的数据,因此不需要通过 storeToRefs() 处理。
十五、Pinia 异步请求的完整例子
// src/stores/user.js
import { defineStore } from 'pinia'
export const useUserStore = defineStore('user', {
state: () => ({
user: null,
loading: false,
error: null
}),
getters: {
isLoggedIn: state => Boolean(state.user),
displayName: state => {
return state.user?.name || '未登录'
}
},
actions: {
async fetchUser(userId) {
this.loading = true
this.error = null
try {
const response = await fetch(
`/api/users/${userId}`
)
if (!response.ok) {
throw new Error('获取用户失败')
}
this.user = await response.json()
return this.user
} catch (error) {
this.error = error.message
throw error
} finally {
this.loading = false
}
},
logout() {
this.user = null
}
}
})
组件:
<script setup>
import { onMounted } from 'vue'
import { storeToRefs } from 'pinia'
import { useUserStore } from '@/stores/user'
const userStore = useUserStore()
const {
user,
loading,
error,
displayName
} = storeToRefs(userStore)
onMounted(() => {
userStore.fetchUser(1001)
})
</script>
<template>
<p v-if="loading">加载中...</p>
<p v-else-if="error">{{ error }}</p>
<p v-else-if="user">你好,{{ displayName }}</p>
</template>
流程比 Vuex 少了一层 mutation:
组件调用 fetchUser
↓
action 发起请求
↓
action 直接修改 user、loading、error
↓
组件响应更新
十六、Pinia Setup Store
Pinia 还支持接近 Composition API 的写法:
import { computed, ref } from 'vue'
import { defineStore } from 'pinia'
export const useCounterStore = defineStore(
'counter',
() => {
const count = ref(0)
const doubleCount = computed(
() => count.value * 2
)
function increment() {
count.value++
}
return {
count,
doubleCount,
increment
}
}
)
对应关系:
ref / reactive → state
computed → getters
function → actions
两种形式的选择:
Option Store → state、getters、actions 分区明确
Setup Store → 接近 Composition API,组织方式更自由
普通业务使用哪一种都可以。学习阶段应能看懂两种形式,但不需要为了显得高级而全部改为 Setup Store。
十七、状态持久化只需要掌握什么
Vuex 和 Pinia 默认保存的是 JavaScript 内存状态。
刷新页面时:
旧页面被销毁
↓
Vue 应用重新创建
↓
store 恢复初始状态
因此状态管理不等于持久化。
如果主题等数据需要刷新后保留,可以配合 localStorage:
const theme = localStorage.getItem('theme') || 'light'
修改时同步保存:
localStorage.setItem('theme', newTheme)
实际项目也可以使用持久化插件,但普通前端首先要掌握三个原则:
只保存确实需要跨刷新的字段;
loading、error 等临时状态不需要保存;
不要无差别把敏感信息写入 localStorage。
登录是否有效和用户是否有权限,最终仍然必须由后端校验,不能只相信前端 store 中的布尔值。
第三部分:Vuex 与 Pinia 对比
十八、同一个计数器的写法对比
Vuex
const store = createStore({
state() {
return {
count: 0
}
},
getters: {
doubleCount(state) {
return state.count * 2
}
},
mutations: {
increment(state) {
state.count++
}
},
actions: {
incrementAsync({ commit }) {
setTimeout(() => {
commit('increment')
}, 1000)
}
}
})
调用:
store.state.count
store.getters.doubleCount
store.commit('increment')
store.dispatch('incrementAsync')
Pinia
export const useCounterStore = defineStore('counter', {
state: () => ({
count: 0
}),
getters: {
doubleCount: state => state.count * 2
},
actions: {
increment() {
this.count++
},
async incrementAsync() {
await delay(1000)
this.count++
}
}
})
调用:
const counterStore = useCounterStore()
counterStore.count
counterStore.doubleCount
counterStore.increment()
counterStore.incrementAsync()
这组对比最直观地体现了两者区别:
Vuex 使用 commit 和 dispatch
Pinia 直接访问属性和调用方法
十九、Vuex 和 Pinia 核心区别
| 对比项 | Vuex 4 | Pinia |
|---|---|---|
| 状态 | state | state |
| 派生状态 | getters | getters |
| 同步修改 | commit mutation | 直接修改或调用 action |
| 异步业务 | dispatch action | 直接调用 action |
| mutation | 有,要求同步 | 没有单独的 mutation |
| 模块组织 | 单一 store 加 modules | 多个独立 store |
| 命名空间 | modules 常配置 namespaced | store id 天然区分 |
| TypeScript | 能支持,但类型写法相对繁琐 | 类型推导通常更自然 |
| 组合式 API | 可以使用 | 使用体验更直接 |
| 常见项目 | 存量 Vue 项目 | 新 Vue 项目 |
1. 数据流不同
Vuex:
dispatch action
↓
commit mutation
↓
修改 state
Pinia:
调用 action
↓
直接修改 state
2. 模块化方式不同
Vuex:
一个根 store
↓
拆分 user、cart 等 modules
Pinia:
useUserStore
useCartStore
useThemeStore
每个 store 本身就是独立业务单元。
3. 调用方式不同
Vuex 通过字符串名称:
store.commit('user/setUser', user)
store.dispatch('user/fetchUser')
Pinia 直接调用方法:
userStore.setUser(user)
userStore.fetchUser()
直接方法调用通常更容易获得编辑器跳转、重构和类型提示。
4. Vuex 并不是不能用了
Vuex 仍然可以正常用于 Vue 项目,大量已有项目也使用 Vuex。只是对于新项目,Pinia 通常具有更简洁的数据流和更自然的组合式 API、TypeScript 使用体验。
二十、项目中应该怎样选择
新项目
通常优先选择 Pinia:
API 更少;
不需要 mutations;
多个 store 的组织方式更直接;
组合式 API 使用自然;
TypeScript 类型推导较好。
已经使用 Vuex 的项目
不需要只因为 Pinia 更新就立即重写整个状态层。
应该考虑:
现有代码是否稳定;
迁移能解决什么真实问题;
测试是否完善;
改造成本和风险是否合理。
维护老项目时,应先理解现有 Vuex 数据流:
state
getters
mutations
actions
modules
新功能是否逐步迁移,再根据团队计划决定。
小型页面
如果只有少量局部状态,可能根本不需要 Vuex 或 Pinia:
const visible = ref(false)
const list = ref([])
不要因为项目使用 Vue,就默认安装一个全局状态库。
二十一、常见误区
1. 所有状态都放入 Store
局部弹窗、输入内容等状态应该留在组件中。
2. Vuex action 直接修改 state
按照 Vuex 的标准数据流,action 应该提交 mutation:
actions: {
updateUser({ commit }, user) {
commit('setUser', user)
}
}
3. 在 Vuex mutation 中请求接口
mutation 应保持同步,异步请求放在 action 中。
4. 混淆 commit 和 dispatch
commit mutation
dispatch action
5. Pinia action 不能异步
Pinia action 同步、异步都可以,并且可以直接修改 state。
6. 直接解构 Pinia state
const { count } = counterStore
可能丢失响应性。需要解构时使用:
const { count } = storeToRefs(counterStore)
7. 认为 Store 会自动持久化
Vuex 和 Pinia 默认都是内存状态,刷新后会重新初始化。
8. 使用 Store 代替后端权限校验
前端状态可以被修改,后端必须独立验证身份和权限。
二十二、状态管理高频面试题
1. 什么是 Vuex
可以这样回答:
Vuex 是 Vue 的集中式状态管理库,使用单一状态树保存应用级响应式状态,核心包括 state、getters、mutations、actions 和 modules。
2. Vuex 中 mutation 和 action 有什么区别
可以这样回答:
mutation 通过 commit 提交,负责同步修改 state;action 通过 dispatch 调用,可以执行异步业务,并通过 commit mutation 修改状态。这样便于开发工具准确追踪每次状态变化。
3. Vuex 为什么要求 mutation 同步
可以这样回答:
Vuex 需要记录每次 mutation 执行前后的状态。如果 mutation 内包含异步逻辑,实际状态变化时间无法和本次提交准确对应,不利于调试和状态追踪,所以异步逻辑应放进 action。
4. Vuex modules 有什么用
可以这样回答:
Vuex 使用单一 store,项目变大后可以通过 modules 按用户、购物车等业务拆分 state、getters、mutations 和 actions。通常配置 namespaced,避免不同模块名称冲突。
5. 什么是 Pinia
可以这样回答:
Pinia 是当前 Vue 新项目常用的状态管理库,以多个独立 store 组织业务状态,每个 store 主要包含 state、getters 和 actions。
6. Pinia state 是响应式的吗
可以这样回答:
是。Option Store 中 state 返回的普通对象会被 Pinia 纳入 Vue 响应式系统;Setup Store 则通常使用 ref 或 reactive 定义状态。
7. Pinia action 能同时处理同步和异步吗
可以这样回答:
可以。Pinia 不再单独区分 mutation,action 可以执行同步或异步业务,并直接修改当前 store 的 state。
8. storeToRefs 有什么用
可以这样回答:
直接解构 Pinia store 的 state 和 getters 可能失去响应性。storeToRefs 会把它们转换成保持响应关联的 refs;actions 是绑定好的方法,可以直接解构。
9. Vuex 和 Pinia 的主要区别
可以这样回答:
Vuex 通常使用 dispatch action、commit mutation 的数据流,并通过 modules 拆分单一 store;Pinia 没有 mutations,actions 可以直接修改 state,并以多个独立 store 组织业务,API 和 TypeScript 使用体验更简洁。
10. 什么状态应该放入 Store
可以这样回答:
多个组件或页面共同使用、需要跨路由保持一致,并具有统一业务操作的状态适合放入 store。只属于单个组件的临时交互状态通常留在组件内部。
11. Store 中的数据刷新后还存在吗
可以这样回答:
默认不存在。Vuex 和 Pinia 状态保存在当前页面的 JavaScript 内存中,刷新会重新创建应用和 store。需要保留时应单独设计浏览器或服务端持久化方案。
12. 新项目应该选择 Vuex 还是 Pinia
可以这样回答:
新 Vue 项目通常优先选择 Pinia,因为数据流更简洁,模块组织和类型推导更自然;已有 Vuex 项目不必盲目重写,应根据维护成本和实际收益决定是否迁移。
二十三、本文知识地图
1. 状态范围
局部状态 → ref / reactive
父子通信 → props / emit
应用级共享状态 → Vuex / Pinia
2. Vuex
state → 原始状态
getters → 派生状态
mutations → 同步修改状态
actions → 业务与异步操作
modules → 拆分大型 store
commit → mutation
dispatch → action
3. Pinia
state → 原始状态
getters → 派生状态
actions → 同步、异步业务并修改状态
4. 最核心区别
Vuex:
action → mutation → state
Pinia:
action → state
5. Pinia 解构规则
state / getters → storeToRefs
actions → 直接解构
6. 秋招优先级
第一优先:什么状态需要全局管理
第二优先:Vuex 五个核心概念和数据流
第三优先:Pinia 三个核心概念和组件使用
第四优先:mutation、action、commit、dispatch
第五优先:storeToRefs 和响应性
第六优先:Vuex modules 与两种方案对比
了解即可:持久化插件、动态模块、SSR 和内部源码
二十四、总结
Vuex 和 Pinia 都用于管理跨组件、跨页面共享的响应式状态,但它们的数据流不同。
Vuex 的核心是:
state 保存状态;
getters 计算派生状态;
mutation 同步修改 state;
action 处理业务和异步逻辑;
modules 拆分大型 store。
异步业务的典型 Vuex 流程是:
组件 dispatch action
↓
action 执行异步操作
↓
action commit mutation
↓
mutation 同步修改 state
↓
组件响应更新
Pinia 保留了 state、getters 和 actions,但取消了单独的 mutation 层:
组件调用 action
↓
action 同步或异步处理业务
↓
直接修改 state
↓
组件响应更新
新项目通常优先使用 Pinia,存量项目则经常遇到 Vuex。对于前端秋招,不仅要会说“Pinia 更简洁”,还要能清楚解释 Vuex 为什么通过 mutation 保证同步状态变更,以及两套方案的完整数据流。
无论使用哪一种状态管理库,都不要把所有数据全局化。局部状态留在组件中,真正需要跨组件、跨页面共享的业务状态再进入 store。