01 · NOTES / WebGIS 开发
第 14 节 · 状态管理全景
2026 年 9 月 1 日
第 14 节 · 状态管理全景
📌 版本信息:Zustand 5.x / Redux Toolkit 2.x / React Context(2026-08-29 核对) 📚 来源:Zustand GitHub | Redux Toolkit 文档 | react.dev · State 管理
一、这一节的目标
- 建立"状态分类学"——先分清状态的种类,再选工具
- 掌握 Zustand 的完整用法(本课程后续项目的全局状态标准件)
- 认识 Redux Toolkit 的形态(招聘要求里的"熟悉 Redux"就是它)
- 会做选型决策并说得出理由
二、状态分类学(选型的第一步)
| 状态种类 | 例 | 放哪 |
|---|---|---|
| 服务端状态 | 地震列表、天气(来自 API、会过期) | 请求层(SWR/React Query——第 13 模块 FastAPI 对接时引入;本课先用 useEffect+useState 体会其痛点) |
| 全局 UI 状态 | 主题、当前用户、地图实例、大屏筛选 | Context(低频)或 Zustand(高频/跨页) |
| 局部状态 | 弹窗开关、输入草稿 | useState(就近原则,别乱上提) |
| URL 状态 | 当前城市 id、筛选参数 | react-router 的 params/searchParams(刷新/分享不丢) |
最常见的架构错误:把服务端状态塞进全局状态库手工同步——第 13 模块你会见到 React Query 如何"化腐朽为神奇",先记住这句话。
三、Zustand 完整用法(本课标准件)
// stores/filter.js —— 创建 store(不用 Provider 包裹!)
import { create } from 'zustand';
export const useFilterStore = create((set, get) => ({
// state
minMag: 0,
region: '全部',
// 更新方法(也在 store 里,组件只管调)
setMinMag: (v) => set({ minMag: v }),
reset: () => set({ minMag: 0, region: '全部' }),
// get:读当前完整 state(用于组合逻辑)
bumpAndLog: () => console.log(get().minMag + 1),
}));
// 组件:像用 useState 一样用,但跨组件共享
function Panel() {
const minMag = useFilterStore((s) => s.minMag); // 选择器:只订阅这一个字段!
const setMinMag = useFilterStore((s) => s.setMinMag);
...
}
// 选择器是精髓:minMag 变化时,只订阅了 minMag 的组件重渲染——
// 没订阅 region 的组件完全不动(对比 Context 的"全体重渲染",第 13 节的机制落点)
常用进阶:persist 中间件一行接 localStorage;slice 模式拆大 store。与 Context 的分工(第 11 节的答案落地):地图实例/主题这类"低频稳定对象"用 Context 也很好;大屏的高频筛选联动用 Zustand。
四、Redux Toolkit 认识(读得懂即可)
招聘常写"熟悉 Redux",指的就是 Redux Toolkit(RTK)——老 Redux 的官方现代化封装:
// RTK 的形态:slice = state + reducers 集中定义
const filterSlice = createSlice({
name: 'filter',
initialState: { minMag: 0 },
reducers: {
setMinMag(state, action) { state.minMag = action.payload; }, // 内部用 Immer 允许"直接改"
},
});
// 组件:const minMag = useSelector((s) => s.filter.minMag); dispatch(filterSlice.actions.setMinMag(5));
为什么不选它做主线:Provider/dispatch/action/reducer 四件套样板多,适合超大型团队(状态逻辑强约束、中间件生态、DevTools 时间旅行)。Zustand 用 1/10 的代码获得 90% 的能力——个人与中小项目的事实标准。面试被问 Redux 时答得出"我了解 RTK 的 slice/Immer 模式,项目里选 Zustand 是因为 XX"即可。
五、选型决策树
状态要跨组件吗?
├─ 否 → useState / useReducer(就地)
├─ 是 → 是 URL 能表达的吗(筛选/翻页/选中 id)?
│ ├─ 是 → 路由参数(刷新可分享)
│ └─ 否 → 变化频率与订阅粒度?
│ ├─ 低频(主题/用户/地图实例)→ Context
│ ├─ 高频/跨页/复杂联动 → Zustand
│ └─ 服务端数据 → 请求层(useEffect 起步 → React Query)
└─ 团队超大/历史包袱 → Redux Toolkit
六、自测题
- 状态四大类分别放哪?"服务端状态"为什么特殊?
- Zustand 的选择器(selector)解决什么问题?机制对应第 13 节的哪个知识点?
- RTK 的 Immer 让你"直接改 state",底层真的改了吗?
- 为什么 URL 也算"状态管理"?
- 把"当前登录用户"放哪?把"鼠标移动坐标"放哪?为什么不同?
参考答案
- 服务端状态→请求层;全局 UI→Context/Zustand;局部→useState;URL 状态→路由参数。服务端状态本质是"远端数据的缓存"(有过期/失效/重取语义),手工同步易错——所以有专门工具。
- 只订阅所选字段,其余字段变化不触发本组件重渲染;机制=React 按需重渲染(祖先更新默认全量,Zustand 在 store 层面就拦住了)。
- 没有——Immer 记录你的"修改草稿",最后生成不可变新对象(写法舒服,语义不变)。
- URL 表达的状态刷新/分享/前进后退都不丢;筛选条件放 URL 是免费的可分享链接。
- 登录用户:低频全局 → Context(或 Zustand 均可);鼠标坐标:高频 → 要么就近 useState,要么直接 ref,绝不能放 Context(全体重渲染灾难)。
七、下一步
状态观建立 → 第 15 节:性能优化,memo/懒加载/Profiler 实操。
TAGSweb