Hooks 与状态管理:useState 到 useTransition,Zustand / RTK / TanStack Query
0. 元信息
- 主题路径:
docs/topics/react-app/subtopics/hooks-and-state-management/README.md - 父主题:
react-app - 主分类:前端与客户端
- 辅助分类:工程技术
- 适合对象:会用 useState / useEffect,但被 stale closure、Context 性能、RQ 缓存策略困扰的开发者
- 建议周期:2~2.5 周
- 前置知识:
components-and-jsx、ES2017+、TypeScript 基础 - 最终目标:能为新应用做状态分层(URL / server / 客户端 / 表单)并选择合适 store
1. 学习路线
Hooks 全部语义 → Context 与选择器 → 客户端 store(Zustand / Jotai / RTK)
→ 服务端状态(TanStack Query)→ 进阶(useTransition / useDeferredValue / useOptimistic / use)
2. 阶段周数分配
精简子主题不固定周数。按 §3 顺序完成,重点把“状态分层”模型跑通。
3. 九阶段表
| 阶段 | 核心知识 | 实践产出 | 学会标准 |
|---|---|---|---|
| 1 | useState、useReducer、useRef | 计数器、Todo 列表 | 能解释 batch 与闭包 |
| 2 | useEffect、useLayoutEffect、cleanup | 订阅、计时器、焦点 | 解释依赖数组与 race condition |
| 3 | useMemo、useCallback、memo | 性能 demo | 用 Profile 验证是否真有效 |
| 4 | useId、useSyncExternalStore | 多实例 ID、外部 store 订阅 | 解释 React 18 并发下“撕裂” |
| 5 | useTransition、useDeferredValue | 过滤 + 慢渲染 | 不阻塞输入 |
| 6 | React 19:use / useActionState / useFormStatus / useOptimistic | 表单与乐观更新 | 解释 Server Action |
| 7 | Context 选型、selector、render 范围 | 主题切换、鉴权 | 不滥用 Context |
| 8 | Zustand / Jotai / RTK 对比 | 同一 demo 三种写法 | 能讲清三者的 store 模型差异 |
| 9 | TanStack Query:staleTime / gcTime / 乐观更新 / 无限滚动 | 商品列表 + 详情 | 能解释“何时触发 fetch” |
4. 第一周任务
精简版省略固定日程。按 §3 顺序做最小 demo,并把“stale closure 复现 + 修复”做成专门一节。
5. 阶段通用验收
精简版省略;每个 Hook 必须能用一段代码复现它的“坑”。
6. 最终验收
精简版省略;以 §3 第 9 阶段和 §9.4 案例口述检查为准。
7. 综合项目
精简版省略;成果并入父主题电商 SPA 的“商品列表 + 详情 + 购物车”状态层。
本主题贡献
- 职责 1(URL / server / client / form 四层状态分层):父主题“商品列表 + 详情 + 购物车”严格按 §1 选型原则落地——筛选条件进 URL(
?category=…&page=…)、商品数据走 TanStack Query(staleTime: 30_000、gcTime: 5 * 60_000)、购物车与 UI 偏好走 Zustand 切片、注册/支付表单用react-hook-form+ Zod;禁止把服务端数据塞进 Zustand,禁止把表单状态塞进 Redux(呼应 §9.4 #10)。 - 职责 2(闭包陷阱 / race / 乐观更新回滚):负责把 §3 阶段 1~9 的“坑”全部跑一遍最小复现并交付修复版本——stale closure(
setInterval打印永远是 0)、RQ 重复 fetch 风暴、Context 高频更新让全子树渲染、useMutation旧请求覆盖新请求(AbortController)、onMutate→onError回滚路径缺失;每个坑配套一段 before/after 代码 + 一个失败→通过的测试。 - 职责 3(React 19 并发与 Server Action 衔接):在父主题“结算 + 提交订单”链路落地
useTransition包住“计算总价”这类长任务、useOptimistic做“立即显示下单成功、失败回滚”、useActionState接 Server Action;并写明“Server Action 成功后必须queryClient.invalidateQueries(['orders'])”的协作契约,防止 §9.4 #10 重演。 - 交付物 1:
examples/hooks-pitfalls/内 6 个最小复现 demo(stale closure / effect 死循环 / Context 性能塌方 / RQ race / 乐观回滚失败 / Server Action 不失效 RQ),每个 demo 都含“before(红线)+ after(绿线)”两份代码与一段 Vitest 断言。 - 交付物 2:
examples/store-compare/同款购物车 demo 三种写法(Zustand selector / Jotai atom / RTK slice),README 给出“包体积 / 渲染粒度 / DevTools 体验”三栏对比表,团队后续选型一目了然。 - 交付物 3:
packages/data-layer暴露useProducts()/useProduct(id)/useAddToCart()等 RQ hooks,统一封装staleTime/gcTime/onMutate/onError/invalidateQueries,业务代码不再裸用useQuery。 - 交付物 4:CI 中跑
pnpm dlx knip检测未使用的 store / selector,防止“dead selector 留在 store 里误导新人”。 - 指标 1:商品列表页 1 分钟内重复点击“刷新 / 切回 tab”,RQ 实际请求次数 ≤1(
staleTime ≥ 30s时命中缓存,呼应 §9.4 #2 / #8)。 - 指标 2:所有
useMutation100% 实现onMutate+onError回滚路径;缺失则 ESLint 规则@tanstack/query/no-unhandled-mutation在 CI 报错。 - 指标 3:购物车 Zustand store 仅暴露 selector(
useCartCount、useCartTotal),全树中“直接订阅整个 store”的写法数量为 0(grep -RE 'useStore\(\)' src/必须空)。
8. 推荐资料
精简版省略;使用 §9.3 和 §9.6 Source。
9. 学习资料汇聚(v0.3 自包含)
9.1 背景与动机
React Hooks 2018 年 RFC、2019 年 16.8 稳定。它把状态、副作用、缓存、订阅统一到函数。React 19 又新增 Actions / use / useOptimistic,把表单 mutation 与异步数据读取拉入“React 原生语义”。
状态管理上,社区共识分成两类:
- 客户端状态(UI、偏好、临时草稿):Zustand(外置 store + selector)、Jotai(原子)、Redux Toolkit(reducer + middleware + RTK Query)。
- 服务端状态(远端数据):TanStack Query(缓存、失效、乐观更新)、SWR、Apollo(GraphQL)。
选型原则:先 URL,再 RQ;需要跨页客户端状态再走 store;表单状态用 useState + react-hook-form,避免上 Redux。
9.2 概念地图
flowchart LR
Hook[Hooks] --> Base[useState/useReducer/useRef]
Hook --> Effect[useEffect/useLayoutEffect]
Hook --> Perf[useMemo/useCallback]
Hook --> Concurrent[useTransition/useDeferredValue]
Hook --> New[use/useActionState/useFormStatus/useOptimistic]
Base --> Context
Context --> Store[外部 store]
Store --> Zustand[Zustand]
Store --> Jotai[Jotai]
Store --> RTK[Redux Toolkit]
Effect --> RQ[TanStack Query]
RQ --> Cache[缓存/失效/乐观更新]
Hook --> RSC[RSC / Server Actions]
9.3 基础知识讲解
9.3.1 论文 / 规范 / RFC
- RFC: React Hooks(2018)——动机、闭包、依赖规则。
- RFC: useTransition ——并发入口。
- React 19 Actions RFC ——
useActionState/useFormStatus/useOptimistic。 - TanStack Query 设计目标 ——把“服务端状态”从应用层抽出来。
9.3.2 书 / 课程
- Epic React(Kent C. Dodds)——Hooks 高级。
- FullStack Open ——Redux + RQ 实战。
- Taming the State in React(Robin Wieruch)——按问题选 store。
- Mark Erikson:Redux 风格指南。
9.3.3 博客 / 文档
- react.dev:Hooks 参考。
- react.dev:
useTransition。 - Zustand README。
- Jotai 文档。
- Redux Toolkit 文档。
- TanStack Query 文档。
- Overreacted: Making setInterval Declarative。
- Overreacted: Why Do React Hooks Rely on Stable Identity?。
- Mark Erikson:Redux 工具链演进。
- TkDodo:React Query 系列(RQ 维护者)。
9.3.4 人物
- Dan Abramov:Hooks / Redux。
- Sebastian Markbåge:React 19 新 Hook。
- Andrew Clark:React core。
- Dominik Dorfmeister(TkDodo):TanStack Query 维护者。
- Daishi Kato:Zustand / Jotai 作者。
- Mark Erikson:Redux 维护者。
9.3.5 方法
- URL > server > client > form:把状态分四层。
- External store via
useSyncExternalStore:避免并发下“撕裂”。 - Selector + shallow:
useStore(s => s.x, shallow)。 - Optimistic with rollback:UI 先动,请求失败回滚。
- Query keys = dependency:键的稳定决定缓存命中。
staleTimevsgcTime:默认 0 / 5min,按需调整。
9.4 经典问题与经典案例
| # | 问题 | 为什么重要 | 最简答案或证据 |
|---|---|---|---|
| 1 | 闭包陷阱:effect 用旧 state | 定时器打印总是 0 | 用 ref 保存最新值,或加依赖 |
| 2 | 重复 fetch 风暴 | 没缓存 / 没 staleTime | RQ staleTime: 30_000 |
| 3 | Context 性能崩塌 | 高频更新让全部子组件渲染 | 切片 store;用 selector;拆 Context |
| 4 | 列表变更触发全表渲染 | 父 store 引用变 | selector + memo |
| 5 | 表单提交 race | 旧请求覆盖新请求 | AbortController;RQ 自动取消 |
| 6 | 乐观更新回滚失败 | 缺 rollback 路径 | onMutate 返回 context,onError 恢复 |
| 7 | useEffect 死循环 | 每次渲染新建依赖 | 用 useMemo 或稳定引用 |
| 8 | 切 tab 回来数据又重新请求 | staleTime 太短 | 提升 staleTime;用 refetchOnWindowFocus: false 按需关 |
| 9 | useTransition 没生效 | 包了同步小计算 | 改用 startTransition 包装 setState |
| 10 | Server Action 改 state 但前端没刷新 | 没触发 RQ invalidation | 在 useActionState 成功后 queryClient.invalidateQueries |
9.5 学习难点
- 概念难点:什么时候 effect、什么时候 event handler、什么时候 RQ。区分“渲染所需数据”vs“事件触发的副作用”。
- 思维难点:状态分层。
window.localStorage也算客户端状态;URL 是公开状态;服务端状态是远端的真相。 - 工程难点:store 选型与边界。常见错配:上 Redux 但只读不写;Context 存整个大对象;RQ 当 Redux 用。
9.6 技术标准与接口
9.6.1 Entity
| 名称 | 版本 | 组织 | 状态 |
|---|---|---|---|
| React | 19 | Meta | 活跃 |
| TanStack Query | 5+ | Tanner Linsley / 社区 | 活跃 |
| Zustand | 4+ | pmndrs | 活跃 |
| Jotai | 2+ | pmndrs | 活跃 |
| Redux Toolkit | 2+ | Redux team | 活跃 |
9.6.2 Scope
- React 不规定状态管理协议。
- Hooks 是 React 18+ 的唯一推荐方式。
- RQ 不替代全局 store;与 Zustand / Jotai 各管一头。
9.6.3 Structure
- 必会 Hook:
useState/useReducer/useEffect/useLayoutEffect/useMemo/useCallback/useRef/useId/useSyncExternalStore/useTransition/useDeferredValue。 - React 19 新增:
use/useActionState/useFormStatus/useOptimistic。 - Zustand API:
create/ selector /subscribe/getState。 - RQ API:
useQuery/useMutation/useInfiniteQuery/useQueryClient/invalidateQueries/setQueryData。
9.6.4 Ecosystem
- 表单:react-hook-form、TanStack Form、Formik、Conform。
- 校验:Zod、Valibot。
- 持久化:zustand/middleware/persist、jotai/utils、redux-persist。
- Devtools:React DevTools、Redux DevTools、Zustand DevTools、RQ DevTools。
9.6.5 Depth Tiers
| 层级 | 可观察能力 |
|---|---|
| L0 | 知道 Hooks / store 存在 |
| L1 | 看得懂 useState / useEffect 示例 |
| L2 | 能用 useReducer 替代小 Redux、用 RQ 处理请求 |
| L3 | 能用 Zustand selector + memo 排性能、能配 RQ staleTime / 乐观更新 |
| L4 | 能设计状态分层、设计自定义 middleware / DevTools |
本计划目标:L3。
9.6.6 Source
- react.dev Hooks Reference。
- TanStack Query Docs。
- Zustand Repo。
- 引用快照:2026-07-30。
10. 常见误区
- effect 里 setState 触发循环
- 闭包用旧 state
- Context 存高频更新
- 用 Redux 管所有状态
- RQ staleTime 默认 0 重复打
- 不取消请求
- 乐观更新不回滚
useMemo当银弹useTransition包了同步计算- 没分 URL / server / client / form 四层。
11. 所有知识点分类
- 编程语言 2. 数据结构与算法 3. 计算机基础 4. 工程技术 5. Web 与后端 6. 前端与客户端 7. 数据与人工智能 8. 项目与职业能力 9. 安全与可靠性
本计划归属:前端与客户端 主 + 工程技术 辅。