Redux、Context 与 React Router — 全局状态与导航¶
这一章讲什么: 两个组件离得远了,state 怎么共享?导航不带整页刷新,怎么做到? 答案三件:Redux(严肃的全局状态)、Context(轻量的传送带)、React Router(URL 路由)。 读完你能判断「这片共享状态该进 Redux 还是 Context」——这是真实项目里天天要做的决定。
1. 为什么组件级 state 不够用¶
React 的 state 是 UI 变化的唯一驱动力1,但它是组件级的,而局部 state 只能从上往下、一层一层当 props 传2。想象一个典型企业应用的登录用户资料: 名字、ID、邮箱——应用里一大片组件都要用。如果每个组件各自取一份、 存自己的本地 state,同一份资料就有了无数份拷贝,谁改了、谁没改,立刻乱套3。
解法只有一个方向:全应用只保留一份,谁需要谁来取。书里管这叫 单一事实源(single source of truth)4。两种实现,本章各讲一个。
组件本地 state 只能父传子,跨层级要层层手递手(prop drilling)
Redux 全局一个 store,分片管理;selector 订阅,只重渲关心的人
Context 内置传送带,免 prop drilling;但值一变,所有消费者都重渲
图说:三者不是竞争关系,是三个尺码。穿多大的脚,选多大的鞋。
2. Redux:一个 store、分片、动作,各就各位¶
Redux 把所有全局数据放在一个对象里,这个对象叫 store5。
store 太大怎么办?切成逻辑分片,每片由自己的 reducer 函数管6。
Redux Toolkit(RTK)把「reducer + 动作 + 初始值」打包成一个 slice,
由 createSlice 创建,并自动生成配套的动作创建函数——老 Redux 手写
样板的地狱就此终结7。
术语一屏清:
| 名词 | 是什么 | 本章主线例子里的角色 |
|---|---|---|
| store | 全局唯一的数据对象 | 装着 profile 那片 |
| slice | 一片状态 + 它的 reducer + 动作 | profileSlice,初值 null(未登录) |
| action | 描述「发生了什么」的普通对象,带 type 字段 |
{type:"profileSlice/setProfileSlice", payload:…} |
| dispatch | 把 action 发给 store 的函数 | dispatch(profileAction) |
| reducer | 纯函数:收到 action,决定自己那片 state 怎么变 | setProfileSlice |
| selector | 从 store 里挑一片数据的函数 | state => state.profile |
reducer 必须是纯函数——同样的输入永远同样的输出,不碰外部世界; action 只描述发生了什么,不描述怎么改;怎么改是 reducer 的事8。
主线例子:登录,把档案写进全局¶
① 用户在表单输入 email/password,点 submit
② useActionState 的 action 跑(这里假装调 API:空转 1 秒)
③ 调自定义 Hook 的 setProfile({id:"1", userName:"dave", …})
④ setProfile 内部:dispatch(setProfileSlice(profile))
→ action 对象飞向 store
⑤ store 把这个 action 发给每个 reducer;
type 对得上的 profileSlice 处理它,state 更新
(底层 Immer 保证「看似直接改,实际是不可变拷贝」[^9])
⑥ 订阅了 profile 的组件(App)被通知 → 重渲 → 页面显示档案 JSON
图说:数据永远单向流动——UI 发意图(dispatch),reducer 算新状态,store 通知订阅者。
两个 TypeScript 巧思值得记住:RootState = ReturnType<typeof store.getState>
用第 03 章的 ReturnType 让根状态类型自动跟着 reducer 增减而更新10;
useAppSelector / useAppDispatch 是把库自带的钩子装上类型的定制版11。
最后在 main.tsx 用 RTK 的 <Provider store={store}> 把 store 注进整棵组件树——
从此任何深度的组件都能 useSelector 取数12。顺带一提:
Redux 的 Provider 内部确实用了 Context,但只用来注入 store 这个实例;
状态订阅走的是 Redux 自己的订阅系统——这就是为什么它能精确重渲13。
什么时候用 Redux:领域状态(domain state)——频繁更新的业务数据。 书里也承认 Zustand 这类更轻的方案近年很流行;选 Redux 的理由是它的严格模式、 开发工具和成熟生态,且「store/reducer/dispatch」这套概念迁到任何状态管理 方案都用得上14。
3. Context:不是状态管理,是传送带¶
Context 解决同一个问题(prop drilling),但机制完全不同:它是 React 内置的 传输机制——不经过中间层,直接把值送到树下任意深的消费者手里15。 没有外部依赖,几行代码 setup。
关键区别在性能:Context 的值一变,每一个消费了这个 Context 的组件都重渲, 不管它用的是不是刚变的那部分16;Redux 靠 selector 订阅,组件只盯自己 那片,别的片变了与它无关17。所以书里给了明确的分工:
Context 装「很少变的环境值」——主题、语言、认证状态、特性开关; Redux(或同类库)装「频繁更新的领域状态」。 生产应用里两者并用很常见18。
React 19 给了两个简化:<Context value=…> 直接当提供者(不用再写
<Context.Provider>);第 06 章的 use 也能读 Context,而且在条件分支里
也能调19。主线例子是主题切换:ThemeProvider 用 state 持有
"dark" | "light",Context 值就是 {theme, setTheme};
ThemeDisplay 用 use(ThemeContext) 取出来,点按钮原地换黑白20。
4. React Router:URL 是组件的门牌¶
React Router 是最常用的前端路由框架;一条路由 = 一个 URL 路径 + 一个 React 组件21。书里选的是 Declarative(声明式)模式——最简单、 和 SPA 最合拍(Router 还有 Framework/Data 模式,与 Remix 合并后能搞 SSR、loader/action 数据加载,那已是全栈方案的领地)22。
骨架三层:
<BrowserRouter> {/* 路由服务提供者(本质是 Context) */}
<Routes> {/* 路由容器 */}
<Route index element={<div>Home</div>} />
<Route path="/a" element={<HomeA />}>
<Route path="c" element={<HomeC />} /> {/* 嵌套:全路径 /a/c */}
</Route>
<Route path="/b" element={<HomeB />} />
</Routes>
</BrowserRouter>
三个进阶件:
- 嵌套路由 +
<Outlet/>:子路由的 path 不带斜杠(c而非/c), 表示它挂在父路由下面;父组件里放一个<Outlet/>,子组件就在那里渲染。 访问/a/c,看到HomeA的内容 +HomeC的内容23。 - 动态段:
path="c/:id?"里的:id是参数,?表示可带可不带; 组件里用useParams<{id: string|undefined}>()取。URL 参数默认是字符串, 所以/a/c/22取出来的 id 是"22"(字符串)而不是 2224。 - 两种导航:
<Link to="/b">渲染成一个<a>,但接进了路由系统 (不会触发整页刷新);useNavigate()给编程式导航——navigate(-1)后退,navigate("/a/c")直接去某个路径25。 - 跨路由传对象:
<Link to="/b" state={{message:"Hello World"}}>塞数据, 目标组件用useLocation().state取——类型是any,用前自己判空26。
5. 作者的判断与证据¶
- 「Redux 的概念适用于任何状态管理方案,所以值得学」是明确的教学判断14; 「Zustand 更轻、近年流行」是对竞争格局的事实性承认。
- 「Context 装环境值、Redux 装领域状态」是作者的使用分工,附带性能机制 (全体重渲 vs selector 订阅)作为理由161718。
- 「React 不做细粒度响应式,而是声明式 + 最小 DOM 更新」是与 Vue/Svelte 对比后给出的定位27。
6. 边界与局限¶
- Redux 的异步没讲:
createAsyncThunk、RTK Query 是真实项目的取数主力, 本章 action 全是同步的假登录;真接 API 要等第 15 章才见分晓。 - Router 的数据模式被一笔带过:loader/action、SSR 是 React Router v7 与 Remix 合并后的主战场,本书只用声明式模式,全栈路由能力不在场。
- Context 的重渲性能陷阱只给了原则,没给解法(把 value 拆分、
拆多个 Context、
useContextSelector之类)——读者遇到「主题一切全体重渲」 时仍需另找资料。
判断(我们的,不是书里的): 本章把 Context 与 Redux 的对照建立在 「值变了谁重渲」这一个性能事实上,干净利落——这比罗列两个库的 API 差异有效得多,因为选型分歧的根源从来不在 API,而在更新粒度。 主线例子(登录写档案)也选得好:同一条数据流被 useActionState(第 06 章)、 slice、dispatch、selector 四个环节各接住一段,旧知识全被复用。 如果错,会错在: 「Context=低频值」的口径在 React 19 之后开始松动—— 随着编译器和细粒度更新方案演进,「Context 只能装主题」的说法可能越来越 保守;但对 2026 年的读者,这仍是不会出错的安全线。
7. 可带走的¶
- 跨层级共享状态的动机:单一事实源,消灭多份拷贝。
- Redux 四件套:store(唯一)、slice(分片)、action(发生了什么)、reducer(纯函数,怎么改)。
- dispatch 之后:每个 reducer 都收到 action,type 对上的才动手。
- createSlice 自动生成动作创建函数;Immer 让「看似直接改」等于「安全不可变」。
RootState = ReturnType<typeof store.getState>——类型跟着 store 自动长。- Context 是传送带不是状态库;值一变全体消费者重渲——装低频值。
- Redux 用 Context 只注 store 实例,订阅走自己的系统,所以能精确重渲。
- React Router:一条路由 = 路径 + 组件;嵌套用
<Outlet/>;动态段:id取出来是字符串。 - 导航两法:
<Link to>声明式,useNavigate()编程式(-1后退)。
8. 原文地图¶
| 主题 | 原书章 | 原文位置 |
|---|---|---|
| 单一事实源的动机 | Chapter 6: Learning Redux, Context, and React Router | text/41-ch06-chapter-6-learning-redux-context-and-react-route.txt:147(搜「multiple copies」) · text/41-ch06-chapter-6-learning-redux-context-and-react-route.txt:155(搜「single source of truth」) |
| reducer 与 action 的定义 | 同上 | text/41-ch06-chapter-6-learning-redux-context-and-react-route.txt:221(搜「pure functions」) · text/41-ch06-chapter-6-learning-redux-context-and-react-route.txt:255(搜「plain object」) |
| slice 与 Immer | 同上 | text/41-ch06-chapter-6-learning-redux-context-and-react-route.txt:498(搜「self-contained unit」) · text/41-ch06-chapter-6-learning-redux-context-and-react-route.txt:518(搜「Immer」) |
| RootState 自动更新 | 同上 | text/41-ch06-chapter-6-learning-redux-context-and-react-route.txt:365(搜「automatically update」) |
| dispatch 与 type 匹配 | 同上 | text/41-ch06-chapter-6-learning-redux-context-and-react-route.txt:738(搜「every reducer」) |
| Provider 注入 store | 同上 | text/41-ch06-chapter-6-learning-redux-context-and-react-route.txt:1017(搜「Provider」) |
| selector 订阅模型 | 同上 | text/41-ch06-chapter-6-learning-redux-context-and-react-route.txt:1077(搜「only the components that care」) |
| Context 全体重渲 vs Redux | 同上 | text/41-ch06-chapter-6-learning-redux-context-and-react-route.txt:1113(搜「performance difference」) · text/41-ch06-chapter-6-learning-redux-context-and-react-route.txt:1117(搜「selector-based subscription」) |
| Context 装什么值 | 同上 | text/41-ch06-chapter-6-learning-redux-context-and-react-route.txt:1119(搜「change infrequently」) |
| React 19 Context 简化 | 同上 | text/41-ch06-chapter-6-learning-redux-context-and-react-route.txt:1131(搜「directly as a provider」) |
| Redux Provider 只用 Context 注实例 | 同上 | text/41-ch06-chapter-6-learning-redux-context-and-react-route.txt:1484(搜「dependency injection」) |
| 路由 = 路径 + 组件 | 同上 | text/41-ch06-chapter-6-learning-redux-context-and-react-route.txt:1514(搜「URL path and its associated」) |
| 嵌套路由与 Outlet | 同上 | text/41-ch06-chapter-6-learning-redux-context-and-react-route.txt:1841(搜「Outlet」) |
| 动态段与 useParams | 同上 | text/41-ch06-chapter-6-learning-redux-context-and-react-route.txt:2187(搜「:id?」) |
| Link 与 useNavigate | 同上 | text/41-ch06-chapter-6-learning-redux-context-and-react-route.txt:1917(搜「Link」) · text/41-ch06-chapter-6-learning-redux-context-and-react-route.txt:1998(搜「useNavigate」) |
| Router 模式之分 | 同上 | text/41-ch06-chapter-6-learning-redux-context-and-react-route.txt:1546(搜「Declarative」) |
-
出处:「Chapter 6: Learning Redux, Context, and React Router」第 95–103 段(
text/41-ch06-chapter-6-learning-redux-context-and-react-route.txt:95,搜「main driver」)。 ↩ -
出处:同上第 127–133 段(
text/41-ch06-chapter-6-learning-redux-context-and-react-route.txt:131,搜「passed down only one way」)。 ↩ -
出处:同上第 139–151 段(
text/41-ch06-chapter-6-learning-redux-context-and-react-route.txt:145,搜「tedious and error-prone」;:147,搜「multiple copies」)。 ↩ -
出处:同上第 153–157 段(
text/41-ch06-chapter-6-learning-redux-context-and-react-route.txt:155,搜「single source of truth」)。 ↩ -
出处:同上第 231–233 段(
text/41-ch06-chapter-6-learning-redux-context-and-react-route.txt:231,搜「single object」)。 ↩ -
出处:同上第 237–247 段(
text/41-ch06-chapter-6-learning-redux-context-and-react-route.txt:239,搜「logical sections」)。 ↩ -
出处:同上第 504–520 段(
text/41-ch06-chapter-6-learning-redux-context-and-react-route.txt:512,搜「generates the matching action creators a」)。 ↩ -
出处:同上第 221–261 段(
text/41-ch06-chapter-6-learning-redux-context-and-react-route.txt:223,搜「describe what happened」)。 ↩ -
出处:同上第 514–520 段与 575–583 段(
text/41-ch06-chapter-6-learning-redux-context-and-react-route.txt:518,搜「Immer」;:583,搜「immutable update」)。 ↩ -
出处:同上第 322–365 段(
text/41-ch06-chapter-6-learning-redux-context-and-react-route.txt:365,搜「automatically update」)。 ↩ -
出处:同上第 397–421 段(
text/41-ch06-chapter-6-learning-redux-context-and-react-route.txt:403,搜「useAppDispatch」;:413,搜「useAppSelector」)。 ↩ -
出处:同上第 994–1065 段(
text/41-ch06-chapter-6-learning-redux-context-and-react-route.txt:1017,搜「Provider」;:1035,搜「regardless of where」)。 ↩ -
出处:同上第 1482–1490 段(
text/41-ch06-chapter-6-learning-redux-context-and-react-route.txt:1484,搜「dependency injection」;:1490,搜「bypass Context」)。 ↩ -
出处:同上第 61–79 段与第 21–31 段(
text/41-ch06-chapter-6-learning-redux-context-and-react-route.txt:75,搜「strict patterns」;:31,搜「solid foundation」)。 ↩↩ -
出处:同上第 1097–1111 段(
text/41-ch06-chapter-6-learning-redux-context-and-react-route.txt:2467,搜「not a state management library」;:1109,搜「transport mechanism」)。 ↩ -
出处:同上第 1113–1117 段(
text/41-ch06-chapter-6-learning-redux-context-and-react-route.txt:1115,搜「every component that consumes」)。 ↩↩ -
出处:同上第 1117 段(
text/41-ch06-chapter-6-learning-redux-context-and-react-route.txt:1117,搜「selector-based subscription」)。 ↩↩ -
出处:同上第 1083–1093 段与 1119–1121 段(
text/41-ch06-chapter-6-learning-redux-context-and-react-route.txt:1119,搜「infrequently」;:1091,搜「both together」)。 ↩↩ -
出处:同上第 1123–1135 段(
text/41-ch06-chapter-6-learning-redux-context-and-react-route.txt:1131,搜「directly as a provider」)。 ↩ -
出处:同上第 1187–1230 段与 1344–1414 段(
text/41-ch06-chapter-6-learning-redux-context-and-react-route.txt:1195,搜「null」;:1348,搜「use(ThemeContext)」)。 ↩ -
出处:同上第 1504–1518 段(
text/41-ch06-chapter-6-learning-redux-context-and-react-route.txt:1514,搜「URL path and its associated」)。 ↩ -
出处:同上第 1546–1548 段与 2439–2451 段(
text/41-ch06-chapter-6-learning-redux-context-and-react-route.txt:1548,搜「Declarative」;:2451,搜「full-stack solution」)。 ↩ -
出处:同上第 1765–1875 段(
text/41-ch06-chapter-6-learning-redux-context-and-react-route.txt:1769,搜「path="c"」);:1846,搜「Outlet」。 ↩ -
出处:同上第 2161–2241 段(
text/41-ch06-chapter-6-learning-redux-context-and-react-route.txt:2187,搜「:id?」;:2239,搜「string」)。 ↩ -
出处:同上第 1891–2160 段(
text/41-ch06-chapter-6-learning-redux-context-and-react-route.txt:1917,搜「Link」;:2031,搜「navigate(-1)」)。 ↩ -
出处:同上第 2326–2393 段(
text/41-ch06-chapter-6-learning-redux-context-and-react-route.txt:2340,搜「state={{ message」);:2360,搜「useLocation」。 ↩ -
出处:同上第 105–121 段(
text/41-ch06-chapter-6-learning-redux-context-and-react-route.txt:109,搜「through fine-grained reactivity (as libr」)。 ↩