返回笔记列表
01 · NOTES / WebGIS 开发

第 14 节 · 状态管理全景

2026 年 9 月 1 日

第 14 节 · 状态管理全景

📌 版本信息:Zustand 5.x / Redux Toolkit 2.x / React Context(2026-08-29 核对) 📚 来源:Zustand GitHubRedux Toolkit 文档react.dev · State 管理

一、这一节的目标

  1. 建立"状态分类学"——先分清状态的种类,再选工具
  2. 掌握 Zustand 的完整用法(本课程后续项目的全局状态标准件)
  3. 认识 Redux Toolkit 的形态(招聘要求里的"熟悉 Redux"就是它)
  4. 会做选型决策并说得出理由

二、状态分类学(选型的第一步)

状态种类 放哪
服务端状态 地震列表、天气(来自 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

六、自测题

  1. 状态四大类分别放哪?"服务端状态"为什么特殊?
  2. Zustand 的选择器(selector)解决什么问题?机制对应第 13 节的哪个知识点?
  3. RTK 的 Immer 让你"直接改 state",底层真的改了吗?
  4. 为什么 URL 也算"状态管理"?
  5. 把"当前登录用户"放哪?把"鼠标移动坐标"放哪?为什么不同?

参考答案

  1. 服务端状态→请求层;全局 UI→Context/Zustand;局部→useState;URL 状态→路由参数。服务端状态本质是"远端数据的缓存"(有过期/失效/重取语义),手工同步易错——所以有专门工具。
  2. 只订阅所选字段,其余字段变化不触发本组件重渲染;机制=React 按需重渲染(祖先更新默认全量,Zustand 在 store 层面就拦住了)。
  3. 没有——Immer 记录你的"修改草稿",最后生成不可变新对象(写法舒服,语义不变)。
  4. URL 表达的状态刷新/分享/前进后退都不丢;筛选条件放 URL 是免费的可分享链接。
  5. 登录用户:低频全局 → Context(或 Zustand 均可);鼠标坐标:高频 → 要么就近 useState,要么直接 ref,绝不能放 Context(全体重渲染灾难)。

七、下一步

状态观建立 → 第 15 节:性能优化,memo/懒加载/Profiler 实操。

TAGSweb