- 前端
【免费下载链接】react-redux
Official React bindings for Redux
React Redux 是 Redux 官方的 React UI 绑定库(official Redux UI binding library for React),它在 React 与 Redux 之间扮演"胶水层"角色,把订阅 store、提取状态、触发重渲染这一整套重复性逻辑封装起来。本文以当前仓库gh_mirrors/re/react-redux中website/versioned_docs/version-6.x/introduction/why-use-react-redux.md为核心骨架,结合src/目录下的真实实现源码,讲清楚"为什么要用 React Redux"的四个核心理由,以及它内部究竟做了什么才值得你信赖。
Redux 与 UI 框架:为什么需要一层"绑定库"
Redux 本身是一个完全独立的库,可以与任何 UI 层或框架配合使用,包括 React、Angular、Vue、Ember,甚至纯 vanilla JS。虽然 Redux 和 React 经常被一起提及,但它们在技术上是相互独立的——Redux 不依赖 React,React 也不依赖 Redux。
正因为如此,当你在任何 UI 框架中使用 Redux 时,通常需要引入一个"UI binding"(UI 绑定)库来把两者连接起来,而不是在 UI 代码里直接操作 store。React Redux 正是 Redux 官方为 React 提供的这套绑定实现。
如果你的疑问是"到底该不该用 Redux",可以阅读当前仓库中 docs/introduction/getting-started.md 与 docs/introduction/why-use-react-redux.md 的配套内容,它们讨论了 Redux 的设计动机与适用场景。
集成 Redux 与 UI 的五个固定步骤
无论是哪个 UI 层,把 Redux 集成进来都需要执行同一套一致的步骤:
- 创建一个 Redux store;
- 订阅(subscribe)store 的更新;
- 在订阅回调内部:
- 获取当前的 store state;
- 提取当前 UI 组件所需要的数据;
- 用这些数据更新 UI;
- 如有必要,用初始 state 渲染 UI;
- 响应用户输入,通过
dispatch派发 Redux action。
这些逻辑完全可以手写,但手写会带来两个问题:
- 高度重复:每个需要读状态的组件都要重复编写订阅、取值、更新的样板代码;
- 性能优化困难:想要避免无谓重渲染,需要自行设计复杂的"数据是否变化"判断逻辑。
而"订阅 store → 检查数据是否更新 → 触发重渲染"这个过程本身是可以被泛化、复用的。React Redux 就是替你处理这整套 store 交互逻辑的绑定库,让你不用自己写这些代码。
从当前仓库源码看,这套逻辑被拆成了清晰的几个模块:
src/components/Provider.tsx负责创建 store 订阅(createSubscription(store))并把{ store, subscription, getServerState }通过 Context 下发到组件树;src/components/connect.tsx中的ConnectFunction通过useSyncExternalStore订阅 store,在订阅回调checkForUpdates里执行"取最新 state → 运行 selector 计算子组件 props → 判断是否变化 → 触发重渲染"的完整链路(src/components/connect.tsx);src/utils/Subscription.ts封装了订阅的生命周期管理、嵌套订阅与通知扩散。
理由一:它是 Redux 官方的 React UI 绑定
虽然 Redux 可以与任何 UI 层配合,但它最初就是为 React 设计、也主要面向 React 使用的。虽然社区里存在针对其他框架的绑定层,但React Redux 由 Redux 团队直接维护。
作为官方绑定,React Redux 会:
- 跟随 React 与 Redux 双方任何 API 变更保持同步更新,确保你的 React 组件行为符合预期;
- 其设计思路遵循 React 的设计原则——编写声明式组件(declarative components)。
理由二:鼓励良好的 React 架构(容器组件 / 展示组件)
React 组件很像函数:把全部逻辑写进一个巨型函数当然可行,但通常更好的做法是拆成若干职责单一的小函数。组件同理——与其编写一个包揽所有任务的巨型组件,不如按职责拆分组件。
在实际项目中,一种常见且被广泛认可的拆分方式是:
- 容器组件(container components):负责收集与管理数据;
- 展示组件(presentational components):纯粹根据收到的 props 渲染 UI。
connect函数会自动生成"容器"包装组件,替你处理与 store 交互的全部过程(src/components/connect.tsx 中_connect→wrapWithConnect就是这一生成逻辑)。这样你的业务组件就可以专注于其他任务——无论是继续收集其他数据,还是仅仅渲染一块 UI。
connect还有两个额外收益:
- 抽象掉"用的是哪个 store":连接组件通过 Context(
ReactReduxContext,见 src/components/Context.ts)读取 store,业务组件因此更具可复用性——它不关心 store 从哪来; - 让你的组件对 Redux"无感知":它们只是像普通 React 组件一样接收数据和函数作为 props。这最终让组件更容易测试、更容易复用。
// 业务组件只关心 props,完全不感知 Redux function TodoItem({ todo, onToggle }) { return <li onClick={() => onToggle(todo.id)}>{todo.text}</li> } // 容器层负责与 store 交互 import { connect } from 'react-redux' const TodoList = connect( (state) => ({ todos: state.todos }), (dispatch) => ({ onToggle: (id) => dispatch({ type: 'TOGGLE_TODO', payload: id }), }), )(TodoItem)注意:当前仓库中
connect已被标记为 deprecated,推荐使用useSelector/useDispatchhooks(见 src/components/connect.tsx 的注释说明),但"容器 / 展示分离"的架构理念并未改变。
理由三:内置大量性能优化,替你省下无谓的重渲染
React 本身很快,但默认情况下,组件树中任何一处更新都会导致该部分组件树内的所有组件重新渲染。如果某个组件的数据根本没变,这次重渲染输出的 UI 完全相同,属于浪费。
因此,性能优化的最佳手段就是跳过不必要的重渲染,让组件只在数据真正变化时重新渲染。React Redux 在内部实现了大量此类优化,确保你的组件"只在真正需要时"重渲染。
3.1 记忆化的 selector 工厂
connect内部通过defaultSelectorFactory(src/connect/selectorFactory.ts)构建一个记忆化(memoized)的 props selector。pureFinalPropsSelectorFactory会缓存上一次的 state、ownProps、stateProps、mergedProps,并根据以下情况分支处理(src/connect/selectorFactory.ts):
- 只有 ownProps 变化 → 只重算依赖 ownProps 的部分;
- 只有 state 变化 → 用
areStatePropsEqual比较 stateProps,没变化就不重新 merge; - 两者都变 → 全量重算;
- 都没变 →直接返回缓存的 mergedProps。
3.2 四个相等性比较函数
connect的 options 提供了四个可配置的相等性比较函数,默认值如下(src/components/connect.tsx):
| 选项 | 默认值 | 作用 |
|---|---|---|
areStatesEqual | strictEqual(===) | 比较前后两次 store state 是否变化 |
areOwnPropsEqual | shallowEqual | 比较前后两次 ownProps 是否变化 |
areStatePropsEqual | shallowEqual | 比较 selector 计算出的 stateProps 是否变化 |
areMergedPropsEqual | shallowEqual | 比较最终 mergedProps 是否变化 |
其中shallowEqual的实现位于 src/utils/shallowEqual.ts,它先做严格相等判断(含NaN与±0的特判),再逐键浅比较对象属性。只有最终 props 判定为"没变"时,React.memo(ConnectFunction)(src/components/connect.tsx)才会阻止下游重渲染。
3.3 嵌套订阅与更新顺序保证
src/utils/Subscription.ts实现了嵌套订阅(nested subscription)机制:每个连接组件都会创建自己的Subscription,并挂到最近的已连接祖先节点上,而不是直接订阅 store。这样能保证祖先组件先于后代组件重渲染,避免子组件在父组件数据尚未就绪时读到不一致的中间状态。同时,trySubscribe/tryUnsubscribe通过引用计数管理订阅生命周期,配合batch实现一次通知批量触发。
3.4 按需提取数据,减少重渲染频率
在组件树中连接多个组件后,每个连接组件只会从 store state 中提取自己真正需要的那些数据片段。由于大多数时候这些片段并没有变化,组件需要重渲染的频率自然大幅降低。
这套理念在 hooks API 中同样成立:useSelector默认使用引用相等(refEquality,即a === b)作为equalityFn(src/hooks/useSelector.ts),配合useSyncExternalStoreWithSelector做到"选中值引用没变就不重渲染"。
理由四:官方绑定带来的社区支持
作为 React 与 Redux 之间的官方绑定库,React Redux 拥有庞大的用户社区。这意味着:
- 遇到问题时更容易获得帮助(社区问答、Discord、Reddit 等渠道讨论丰富);
- 更容易学习最佳实践;
- 大量第三方库基于 React Redux 构建,可以直接复用;
- 你在一个应用里积累的知识可以复用到不同项目中。
结合源码理解整体数据流
把上述内容串起来,一次完整的"store 更新 → UI 刷新"流程在源码层面是这样的:
- 某个组件
dispatch(action); - Redux store 状态变化,通知订阅者;
Provider创建的顶层Subscription(src/components/Provider.tsx)收到通知,沿嵌套订阅链向下传播;- 每个连接组件的
checkForUpdates(src/components/connect.tsx)读取最新 state,运行记忆化 selector 计算新 child props; - 若 props 未变化,仅向下级扩散通知(
notifyNestedSubs);若变化,则通过useSyncExternalStore触发 React 重渲染; React.memo+ 元素引用记忆化(src/components/connect.tsx)进一步确保未变化的子组件不被重渲染。
hooks 路径(useSelector)则直接复用subscription.addNestedSub订阅,并用equalityFn决定是否触发重渲染(src/hooks/useSelector.ts)。
进一步阅读
- docs/introduction/why-use-react-redux.md:当前版本(version 6.x 之后)的"为什么使用"文档;
- docs/introduction/getting-started.md:快速上手指南;
- docs/api/connect.md 与 docs/tutorials/connect.md:
connect的完整 API 与用法; - docs/api/hooks.md 与 docs/using-react-redux/accessing-store.md:hooks 方式的现代用法;
- docs/using-react-redux/connect-extracting-data-with-mapStateToProps.md 与 docs/using-react-redux/connect-dispatching-actions-with-mapDispatchToProps.md:
mapStateToProps/mapDispatchToProps的深入讲解; - 源码方面,核心实现集中在 src/components/connect.tsx、src/components/Provider.tsx、src/connect/selectorFactory.ts、src/utils/Subscription.ts 与 src/hooks/useSelector.ts,测试用例可参考 test/components/connect.spec.tsx 与 test/components/Provider.spec.tsx。
总结
React Redux 存在的意义可以概括为三点:官方背书保证兼容性与正确性(由 Redux 团队维护并同步双方 API)、架构收益(通过connect或 hooks 把 store 交互从业务组件中剥离,得到易测试、易复用的声明式组件)、以及性能收益(记忆化 selector、浅比较、嵌套订阅、按需提取数据等多层优化)。无论你选择经典的connect还是现代的 hooks API,这套价值主张始终成立——这也是为什么"使用 Redux 与 React 时,应该同时使用 React Redux"会成为一个广泛遵循的实践。
- 前端
【免费下载链接】react-redux
Official React bindings for Redux
相关推荐
5分钟掌握Super IO:Blender剪贴板导入导出终极指南
5分钟掌握Super IO:Blender剪贴板导入导出终极指南 还在为Blender繁琐的导入导出操作而烦恼吗?每次都要在多层菜单中寻找格式选项,重复点击确认
前端从收藏夹到知识库:如何用BiliTools让B站视频变成你的个人学习资产
从收藏夹到知识库:如何用BiliTools让B站视频变成你的个人学习资产 你是否也曾被B站海量的优质内容淹没?那些收藏了却从未观看的教程视频,那些想要重温却找不
桌面应用音视频Redux项目核心思想:为什么要使用状态管理
Redux项目核心思想:为什么要使用状态管理 前言:现代前端开发的复杂性挑战 在当今的前端开发环境中,单页面应用(SPA)的复杂度已经达到了前所未有的高度。一个
前端
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考