React / Redux 是怎样实现响应式的?

作为一个 vue3 转 react 19 的萌新,我一直很好奇 react 是怎么实现响应式数据的。
这个问题在我学习 redux 的时候,变得尤其突出。下面,我会用实际的案例说清楚这件事。在文末,我会通过这件事,中立的思考两个框架的设计哲学。
案例背景
我的环境是 React 19,并且使用了社区推荐的 Ducks 风格组织 redux 代码,本文中的所有代码都将采用目前为止社区推荐的最现代化的方案。
[!note]- 关于 Ducks: Redux Reducer Bundles
很多老教程组织 redux 的方式和 ducks 大相径庭,关于这件事可参考以下资源:
下面正式开始。我先创建这样的文件:
src/App.tsx
:
1 | import {useAppDispatch, useAppSelector} from "./app/hooks"; |
经过一系列机制,count 值会被更新,然后响应式的显示在页面上。不过写到这行代码的时候,我确实感到困惑不已:
1 | const count = useAppSelector(state => state.counter.value) |
很明显,count 就是一个 number 类型的东西,它没经过 vue 里的 ref 或者 reactive 处理,那它为什么能实现响应式?
我从心智模型角度,先说结论,然后再用真实机制辅助理解。
心智模型转换
Vue 开发者思维:
“我要修改这个数据,视图会自动更新”
React 开发者思维:
“我要触发重新渲染,让组件函数重新执行,获取最新数据”
关键区别:React 没有"响应式变量,而是利用重新渲染机制"
React 响应式详解
还是回到代码:
1 | const count = useAppSelector(state => state.counter.value) |
按照顺序解释这件事:
1. useAppSelector
会自动订阅 Redux Store
这里的订阅的作用是,在之后 Redux Store 发生变化的时候,其会通知所有订阅者做一些事。
2. 每当一个 action 被 dispatch 后,Reducer 会更新 State
Reducer 是后厨,是真正做事的人,其内部的函数例如 amountAdded
有修改 state 的具体逻辑。
3. state 发生了变化,因此 Store 通知所有订阅的组件
如第一点所说,useAppSelector 也是订阅者之一,它被通知后,会做两件事:
-
重新运行 Selector 并比较结果:
useSelector 会重新运行你传入的 selector 函数(在这里是state => state.counter.value
),得到一个新的结果。 -
决定是否重渲染: 它会用严格相等 (
===
) 的方式比较 上一次 selector 返回的结果和 这一次 selector 返回的结果。-
如果两个结果不相等 (previousResult !== newResult),useSelector 就会触发组件的重新渲染。
-
如果两个结果相等,它就什么也不做,组件将不会重新渲染。
-
4. 组件重新渲染
在这个例子中,state.counter.value 确实发生了变化,于是 useSelector
就会迫使组件重新渲染,也就是让 App() 函数重新执行。
此时 useAppSelector 内部的 selector 函数自然也会被重新执行,因此 count 得到了 store 中最新的值,在 JS 层面值就被更新了。
而下面 return JSX 的语句也自然会被重新执行,此时已经是用最新的 count 了,那么 UI 看起来就是被更新了。到这里,完成了响应式的闭环。
one more thing: 设计哲学的思考
我有这样的困惑:
Redux store 的 state 更新了为什么要通知所有订阅者?通知用到了对应 state 的订阅者不就行了吗?通知所有订阅者,所有订阅者又要进行一次 === 判断, 万一订阅者一多,这不是造成了极大的性能浪费吗?在 vue 中似乎就不会有这个问题。react 为什么要这样设计?
这个不起眼的困惑,极大加深了我对 react 及其周边生态设计哲学的理解。
我总结的回答如下。
简单和可预测
Redux 的核心设计哲学是极致的简单和可预测性。
-
一个“愚蠢”的 Store:Redux Store 的职责非常单一:
-
持有一个 state 对象。
-
提供一个 getState() 方法来读取它。
-
提供一个 dispatch(action) 方法来触发更新。
-
提供一个 subscribe(listener) 方法来注册监听器。
-
请注意,Store 完全不知道也不关心 state 对象的内部结构。对它来说,state 就是一个黑盒。它只知道当 dispatch 发生后,根 state 对象被替换成了一个新的对象。
- “广播”是最简单的通知机制:既然 Store 不知道 state 的内部结构,它就无法做到“精确通知”。最简单、最可靠、最解耦的办法就是:“嘿,各位!我这里更新了,你们自己看看有没有你们关心的东西吧!”
这种“ dumb store, smart components (愚蠢的 store,聪明的组件)”的设计,让 Redux 核心库本身可以非常小、非常稳定、几乎没有 bug。它把“判断是否需要更新”的智能部分,推迟到了订阅者(也就是 React-Redux)这一层。
性能权衡:另一种智慧
“万一订阅者一多,这不是造成了极大的性能浪费吗?”
答案是:这种浪费的成本极低,而它所避免的成本极高。
-
Selector 和 === 比较的成本极低:
运行一个简单的选择器函数(如 state => state.counter.value)和进行一次引用或值的严格相等比较(===
),是 JavaScript 中最快的操作之一,通常在纳秒级别。即使有几百个组件在做这个操作,对于现代 JS 引擎来说总耗时也微不足道,远远低于用户能感知到的延迟。 -
真正昂贵的成本是组件渲染:
性能的瓶颈几乎永远在于组件的重新渲染(Re-render),因为它涉及到创建虚拟 DOM、进行 Diff 算法比较、以及最终可能的真实 DOM 操作。
所以,Redux 的整个模型就是用成百上千次廉价的 === 比较,来避免哪怕一次昂贵的组件渲染。这笔交易在性能上是极其划算的。
与 Vue 的对比:不同的哲学
-
Vue 的响应式系统 (Reactivity System):Vue (无论是 2.x 的 defineProperty 还是 3.x 的 Proxy) 采用的是一种 “精细化追踪” 或 “依赖收集” 的模式。
-
依赖收集:当你的组件在渲染时访问了 state.counter.value,Vue 会“记录”下来:“哦,这个组件依赖于 state.counter.value 这个具体的数据点。”
-
精确派发:当 state.counter.value 的值被修改时,Vue 的系统会精确地找到所有记录在案的、依赖这个数据点的组件,然后只通知这些组件去更新。
-
这套系统复杂、自动化、有点“魔法”。框架在底层为你做了很多事,这使得开发者基本不用关心依赖关系,框架自动处理。
但是,有时在复杂场景下,追踪“为什么更新”会变得困难。
React 和 Redux 选择的这条路,是 “明确优于隐式” (Explicit is better than implicit) 哲学的一种体现。
它强迫开发者明确声明组件的数据依赖,使得整个应用的数据流向变得极为清晰和可预测。
虽然它在通知阶段采用了看似“暴力”的广播模式,但通过极其廉价的比较操作,高效地守住了性能的大门——避免不必要的组件渲染。
而 Vue 则选择了另一条路,它认为响应式处理是框架的责任,应该为开发者尽可能自动化,提供了更“智能”的开箱即用体验。
通过这件事,我理解了为什么大厂都选择用 react。因为其把更多实现细节交给开发者,在带来开发复杂度的同时,也让开发者能更自由的掌控他们的应用。
这也是为什么 react 更接近原生 JS,它没有封装太多东西和实现。而 vue 做了很多自动化的东西,这要求你明白他们的那一套事情,而不是把选择权交给你,尽管因此 vue 会让人觉得更加“现代化”一点。
很多人总喜欢争论两个框架的优劣,但是在我看来两种方案都非常优秀,并且都在各自的生态中被证明是极其成功的。而怎么选择由你决定,我很高兴技术的多样化,它给我了选择的机会。
- 标题: React / Redux 是怎样实现响应式的?
- 作者: 三葉Leaves
- 创建于 : 2025-08-07 00:00:00
- 更新于 : 2025-08-15 12:08:24
- 链接: https://blog.oksanye.com/6cf7ca3b62f6/
- 版权声明: 本文章采用 CC BY-NC-SA 4.0 进行许可。