4、状态管理层设计:全局状态与局部状态的区分、Redux/Zustand/Context API的选型依据、状态管理层的分层策略
状态管理,说白了就是解决「数据该放哪」的问题。
我见过太多项目,一开始图省事,把所有数据都塞进全局 store。结果呢?组件一多,store 变得又大又乱,改一个字段要排查半天。反过来,也有人把所有状态都写在组件里,跨组件通信全靠 props 层层传递,改个需求能改到怀疑人生。
所以,这一章我们来聊聊状态管理层到底该怎么设计。我会结合自己踩过的坑,把全局状态和局部状态的区分、工具选型、以及分层策略讲清楚。
4.1 全局状态 vs 局部状态:先分清楚再动手
很多新手容易犯一个错:觉得「用 Redux 就是专业」。其实不是的。状态该放哪,取决于它的「作用域」和「生命周期」。
核心原则:能用局部状态解决的问题,绝不上升到全局。
我习惯这样区分:
- 局部状态:只在一个组件或少数几个兄弟组件里用。比如表单输入框的值、下拉菜单的展开收起、弹窗的显示隐藏。这些状态用
useState或useReducer就够了。 - 全局状态:多个不相关的组件都需要读写。比如用户登录信息、主题配置、购物车数据、多步骤表单的共享数据。这些才需要放到全局 store 里。
举个例子。我之前做过一个后台管理系统,有个「用户列表」页面。列表的筛选条件、分页页码,这些其实只需要在列表组件内部用。但当时我一股脑全放进了 Redux。后来发现,每次切换页面再回来,筛选条件还保留着,用户反而觉得奇怪。嗯,这就是典型的「过度全局化」。
我的建议:先写局部状态。当发现「这个状态需要被两个以上不相关的组件共享」时,再考虑提升到全局。别一开始就铺全局 store。
4.2 选型依据:Redux / Zustand / Context API 怎么选?
这个问题几乎每次技术评审都会遇到。我直接说结论:
| 工具 | 适用场景 | 我个人的看法 |
|---|---|---|
| Context API | 低频更新的全局状态,比如主题、语言、用户信息 | 简单轻量,但别滥用。Context 更新会导致所有消费组件重新渲染,性能是个坑。 |
| Zustand | 中小型项目,状态逻辑不复杂,追求开发效率 | 我最近很喜欢用。API 简洁,没有样板代码,性能也不错。 |
| Redux Toolkit | 大型项目,状态逻辑复杂,需要中间件(如异步请求、日志) | 虽然重,但生态成熟。团队协作时,强约束反而能减少混乱。 |
为什么会这样?我展开说说。
4.2.1 Context API:简单但别滥用
Context 是 React 自带的,不需要额外安装。但你要知道,Context 的更新是「广播式」的——只要 value 变了,所有消费这个 Context 的组件都会重新渲染,不管它们是不是只关心其中一部分数据。
我曾经在一个项目里用 Context 管理用户信息 + 通知列表。结果每次通知列表更新,连头像组件都跟着重渲染。后来我拆成了两个 Context,才解决问题。所以,Context 适合低频更新的全局数据,比如主题、语言包。
4.2.2 Zustand:轻量又够用
Zustand 是我这两年用得最多的状态管理库。它的核心思想很简单:一个 store 就是一个 hook,直接读写,没有 Provider 包裹,没有 reducer 样板代码。
import { create } from 'zustand'
const useStore = create((set) => ({
count: 0,
increment: () => set((state) => ({ count: state.count + 1 })),
}))
// 在组件里直接用
function Counter() {
const count = useStore((state) => state.count)
const increment = useStore((state) => state.increment)
return <button onClick={increment}>{count}</button>
}
你看,就这么几行。没有 action type,没有 dispatch,没有 Provider。而且 Zustand 默认做了「细粒度订阅」——只有你 select 的那部分数据变了,组件才会重渲染。性能上完全不用担心。
避坑指南:Zustand 虽然灵活,但太灵活也容易写乱。我建议团队里约定好:每个 store 只管理一个领域的数据,比如 useUserStore、useCartStore,别搞一个超级大 store。
4.2.3 Redux Toolkit:大项目的「安全带」
Redux 被吐槽最多的就是样板代码多。但 Redux Toolkit(RTK)已经大大简化了这个问题。它内置了 immer、thunk,还提供了 createSlice 这种好用的 API。
我为什么说它是「安全带」?因为 Redux 的强约束——单向数据流、纯函数 reducer、不可变数据——在大型团队里反而能减少很多低级错误。你想想看,如果每个人都能随意修改 store,那排查 bug 得多痛苦。
import { createSlice, configureStore } from '@reduxjs/toolkit'
const counterSlice = createSlice({
name: 'counter',
initialState: { value: 0 },
reducers: {
increment: (state) => { state.value += 1 },
},
})
const store = configureStore({
reducer: { counter: counterSlice.reducer },
})
RTK 还支持中间件,比如 redux-saga 或 redux-observable。如果你的项目有复杂的异步流程(比如 WebSocket 重连、多步骤表单提交),Redux 的中间件生态是其他工具比不了的。
注意:别为了用 Redux 而用 Redux。如果你的项目只有两三个页面,Zustand 或 Context 完全够用。Redux 的学习成本和维护成本是真实存在的。
4.3 状态管理层的分层策略
好了,工具选完了,接下来是「怎么组织」的问题。我习惯把状态管理层分成三层:
- UI 层状态:组件内部的状态,用
useState或useReducer。 - 领域层状态:业务相关的全局状态,用 Zustand 或 Redux。
- 服务层状态:服务端数据(比如 API 返回的数据),用 React Query 或 SWR。
这三层各司其职,互不干扰。我详细说说。
4.3.1 UI 层状态:组件自己的事
UI 层状态只跟「交互表现」有关。比如:
- 弹窗是否显示
- 下拉菜单是否展开
- 当前选中的 tab
- 表单输入的值
这些状态用 useState 就够了。别把它们放到全局 store 里,否则你会发现自己写了很多「清理状态」的代码。
4.3.2 领域层状态:业务的核心数据
领域层状态是「跨组件共享的业务数据」。比如:
- 当前登录的用户信息
- 购物车中的商品列表
- 多步骤表单的填写进度
这些数据用 Zustand 或 Redux 管理。我建议按领域拆分 store,比如:
// stores/userStore.js
const useUserStore = create((set) => ({
user: null,
setUser: (user) => set({ user }),
logout: () => set({ user: null }),
}))
// stores/cartStore.js
const useCartStore = create((set) => ({
items: [],
addItem: (item) => set((state) => ({ items: [...state.items, item] })),
removeItem: (id) => set((state) => ({ items: state.items.filter(i => i.id !== id) })),
}))
这样每个 store 职责清晰,维护起来也方便。
4.3.3 服务层状态:跟后端打交道的事
服务层状态指的是「从 API 获取的数据」。很多人喜欢把 API 返回的数据也放进 Redux,然后手动管理 loading、error、缓存。其实没必要。
我推荐用 React Query 或 SWR 来处理服务层状态。它们会自动帮你做缓存、去重、后台刷新、错误重试。你只需要写一个 fetch 函数就行。
import { useQuery } from '@tanstack/react-query'
function UserProfile({ userId }) {
const { data, isLoading, error } = useQuery({
queryKey: ['user', userId],
queryFn: () => fetch(`/api/users/${userId}`).then(res => res.json()),
})
if (isLoading) return <div>加载中...</div>
if (error) return <div>出错了</div>
return <div>{data.name}</div>
}
你看,loading、error、数据缓存全都不用自己管。而且 React Query 还支持乐观更新、分页、无限滚动,非常强大。
核心思路:服务端数据不要放进全局 store。让 React Query 管理它的生命周期,你的 store 只管理「客户端业务状态」。这样职责清晰,代码也干净。
4.4 总结一下
状态管理层设计,说白了就是「分清楚、选对工具、分层管理」。
- 分清楚:局部状态用
useState,全局状态才进 store。 - 选对工具:低频更新用 Context,中小项目用 Zustand,大型复杂项目用 Redux Toolkit。
- 分层管理:UI 层、领域层、服务层各管各的,别混在一起。
我见过太多项目因为状态管理混乱导致后期维护成本飙升。希望这一章的内容能帮你少走一些弯路。下一章,我们会聊聊「数据流层设计」,看看数据在分层架构里到底是怎么流动的。