状态管理方案
What — 是什么
状态管理解决跨组件共享数据的同步问题,从 Redux 的单一 store 到 Zustand/Pinia 的轻量方案,现代前端状态管理越来越简洁。
核心概念:
- Store:集中存储共享状态
- Dispatch/Action:描述状态变更的意图
- Selector:从 store 中精确读取需要的状态
- Middleware:处理异步逻辑(Redux Thunk/Saga)
主流方案:
| 方案 | 生态 | 体积 | 学习成本 | 适用场景 |
|---|---|---|---|---|
| Redux Toolkit | React | 中 | 中 | 大型项目、强约束 |
| Zustand | React | 极小 | 低 | 中小型项目 |
| Jotai/Recoil | React | 小 | 低 | 原子化细粒度 |
| Pinia | Vue | 小 | 低 | Vue 3 官方推荐 |
| Vuerx | Vue | 中 | 中 | Vue 2 遗留项目 |
关键特性:
- Redux:单一数据源、纯函数 reducer、可预测
- Zustand:无 boilerplate、hook 原生、性能好
- Pinia:Vue DevTools 集成、TypeScript 友好、模块化
Why — 为什么
适用场景:
- 多组件共享状态(用户信息、购物车、主题)
- 跨页面状态持久化
- 复杂异步数据流
何时不需要:
- 仅父子组件通信 → props/emit
- 仅跨层级传递 → Context/provide-inject
- 局部 UI 状态 → 组件内 useState/ref
优缺点:
- ✅ 优点:
- 集中管理,状态可追踪
- 组件解耦,不依赖层级关系
- DevTools 支持时间旅行调试
- ❌ 缺点:
- 增加复杂度,小项目过度设计
- 全局 store 易变成”大杂烩”
- 状态与组件耦合度降低,心智负担增加
How — 怎么用
Zustand(React 推荐)
import { create } from 'zustand';
const useStore = create((set) => ({
user: null,
cart: [],
login: (user) => set({ user }),
logout: () => set({ user: null }),
addToCart: (item) =>
set((state) => ({ cart: [...state.cart, item] })),
cartTotal: () =>
useStore.getState().cart.reduce((sum, i) => sum + i.price, 0),
}));
// 组件中使用
function Cart() {
const cart = useStore((s) => s.cart); // 精确选择,避免不必要渲染
const addToCart = useStore((s) => s.addToCart);
return (
<div>
{cart.map(item => <div key={item.id}>{item.name}</div>)}
<button onClick={() => addToCart({ id: 1, name: 'Book', price: 29 })}>
加入购物车
</button>
</div>
);
}
Pinia(Vue 推荐)
// stores/user.js
import { defineStore } from 'pinia';
export const useUserStore = defineStore('user', {
state: () => ({
user: null,
token: localStorage.getItem('token'),
}),
getters: {
isLoggedIn: (state) => !!state.token,
},
actions: {
async login(credentials) {
const res = await api.login(credentials);
this.user = res.user;
this.token = res.token;
localStorage.setItem('token', res.token);
},
logout() {
this.user = null;
this.token = null;
localStorage.removeItem('token');
},
},
});
// 或 Composition API 风格
export const useUserStore = defineStore('user', () => {
const user = ref(null);
const token = ref(localStorage.getItem('token'));
const isLoggedIn = computed(() => !!token.value);
async function login(credentials) { /* ... */ }
function logout() { /* ... */ }
return { user, token, isLoggedIn, login, logout };
});
<script setup>
import { useUserStore } from '@/stores/user'
const userStore = useUserStore()
</script>
<template>
<div v-if="userStore.isLoggedIn">
{{ userStore.user.name }}
<button @click="userStore.logout()">退出</button>
</div>
</template>
Redux Toolkit(大型 React 项目)
import { createSlice, configureStore } from '@reduxjs/toolkit';
const cartSlice = createSlice({
name: 'cart',
initialState: { items: [] },
reducers: {
addItem: (state, action) => {
state.items.push(action.payload); // Immer 允许直接修改
},
removeItem: (state, action) => {
state.items = state.items.filter(i => i.id !== action.payload);
},
},
});
const store = configureStore({
reducer: { cart: cartSlice.reducer },
});
// 组件中使用
import { useSelector, useDispatch } from 'react-redux';
const items = useSelector((s) => s.cart.items);
const dispatch = useDispatch();
dispatch(cartSlice.actions.addItem({ id: 1, name: 'Book' }));
常见问题与踩坑
| 问题 | 原因 | 解决方案 |
|---|---|---|
| Zustand selector 导致不必要渲染 | 返回新对象引用 | 用 shallow 比较或返回原始值 |
| Pinia action 中 this 指向 | action 是普通函数,this 正确绑定 | 不要用箭头函数定义 action |
| Redux boilerplate 多 | 传统 Redux 写法繁琐 | 用 Redux Toolkit 的 createSlice |
| 全局 store 臃肿 | 所有状态放一个 store | 按领域拆分多个 store/slice |
最佳实践
- React 中小项目用 Zustand,大型用 Redux Toolkit
- Vue 3 一律用 Pinia
- 按领域拆分 store,不要一个全局大 store
- 组件局部状态不放全局 store
面试题
Q1: 什么时候需要引入状态管理?
当多个不相关组件需要共享同一份数据(如用户信息、主题)、跨页面状态需要持久化、组件层级过深导致 props 透传(prop drilling)严重时,应引入状态管理。仅父子通信或局部 UI 状态不需要。
Q2: Redux 的核心工作流程是什么?
单向数据流:组件 dispatch(action) → reducer 纯函数处理 action 返回新 state → store 更新 → 组件通过 selector 订阅新 state 重新渲染。异步操作通过 middleware(Thunk/Saga)处理。
Q3: Zustand 相比 Redux 有什么优势?
① 无 boilerplate,无需 action/reducer/slice 模板代码;② 基于 Hook,组件内直接
useStore(s => s.xxx);③ 内置 selector 优化,默认浅比较避免不必要渲染;③ 体积极小(~1KB),无需 Provider 包裹。
Q4: 服务端状态和客户端状态有什么区别?
服务端状态来自远程 API,是异步的、可能过期的、可能有多个消费者共享的副本,适合用 React Query/SWR 管理(自动缓存、失效、重请求);客户端状态存在于浏览器内存,是同步的、完全可控的,适合 Zustand/Pinia 管理。两者不应混在同一个 store。
Q5: 为什么说”全局 store 易变成大杂烩”?如何避免?
所有状态都塞进一个 store 导致职责不清、难维护、按需加载困难。避免方式:按领域(user/cart/theme)拆分多个独立 store/slice;区分服务端状态(React Query)和客户端状态(Zustand);局部 UI 状态留在组件内。
相关链接: