React 中对响应式的理解
最近在专研 React 文档,遇到文档中这样的一句话让我陷入了沉思:“在组件主体中声明的所有变量都是响应式的。”
基于 Vue,我对响应式的理解是这样的:响应式变量是被 Vue 的响应式系统(基于 Proxy)追踪的,只要它们发生变化,Vue 就会自动更新视图。比如通过 ref() 或 reactive() 创建的对象,计算属性 computed,甚至是 watchEffect() 等副作用监听器,都是 Vue 响应式系统的一部分。(后期补充:类似这样的东西在React中被称为响应式值)
但在 React 文档中提到的“响应式”,与 Vue 的“响应式”完全不是一个概念。
React 的“响应式”究竟是什么?
React 不像 Vue 使用 Proxy,也没有自动依赖追踪。它的“响应式”是基于一个核心原则:组件函数每次渲染都会从头重新执行。
也就是说,组件函数就像一个纯函数,每次由外部状态(props 或 state)驱动重新执行整个函数体。于是,在函数体中声明的变量或表达式,每次渲染都会重新赋值、重新计算。
举个例子:
function App() {
const [count, setCount] = useState(0);
const doubled = count * 2; // 每次渲染都会重新计算
return (
<div>
<p>{doubled}</p>
<button onClick={() => setCount(c => c + 1)}>+1</button>
</div>
);
}
在这里,doubled 不是由 useState 创建的,但它确实是“响应式”的 —— 每当 count 改变时,组件重新执行,doubled 也会被重新计算。React 就是通过这种“重新执行组件函数”的方式来保持响应性。
在React中,“响应式”指的是“随渲染重新计算”,而不是自动触发更新!组件主体中,即使写了一个let a = 1,后面没有任何使用和改变,这个a也是响应式。因为组件重新渲染的时候,组件函数重新执行,也会执行let a 和给a赋值为1,这样其实也算作重新计算。这就是为什么Reat会说“在组件主体中声明的所有变量都是响应式的” 的原因。
useEffect 中的依赖项与响应式变量的关系
理解了 React 的响应式机制后,一个非常关键的问题就出现了:**如果一个变量是在组件中计算得出的,并在 useEffect 中使用了,我是否需要将它写入依赖项?**答案是:需要!
React 并不会自动追踪你用了哪些变量。因此,你在 useEffect 中用到的所有变量(无论是 props、state 还是 let/const 声明的计算值),都应该显式地写入依赖数组,否则副作用可能不会按预期重新执行。
这是一个常见误区:很多人以为 “state 变了组件会重新渲染,useEffect 也就会重新执行”。但事实上:
- 如果 useEffect 的依赖数组是空的([]),它只在挂载时执行一次;
- 如果依赖数组中没有写你用到的变量,即使这些变量值变化了,Effect 也不会重新执行;
- 如果不传第二个参数(依赖项),Effect 会在每次渲染后都执行,虽然能确保同步,但这会带来性能隐患和不可预测的行为。
因此,最推荐的方式是:
只要你在 Effect 中使用了一个值,就应该把它写进依赖数组中。哪怕这个值是由 state 或 props 计算得来的。
响应式 ≠ 会触发更新
在 React 中,即使组件中某个值是“响应式的”,它并不会触发更新。换句话说:
组件体内的变量(如 let a = 1)虽然在每次渲染中都会重新赋值,但它本身不会引发组件重新渲染;
想让变量的变化驱动视图更新,仍然需要 useState、useReducer 等显式的“状态”工具;
响应式表达式 ≠ 响应式状态源。
这与 Vue 的响应式设计也形成鲜明对比:Vue 中的响应式变量是可追踪的,它们的变化会自动驱动视图更新;React 中的响应式变量是“重新计算”的,但不会自动触发渲染,只能在渲染中被重新赋值使用。
总结
在 React 中:
- “响应式”指的是变量会随着组件重新渲染而重新赋值;
- 它不等于自动追踪依赖或自动触发更新;
- 在 useEffect 中使用的所有值都必须显式地写入依赖数组;
- 响应式表达式 = 会随着渲染变化而重新计算的表达式;
- 响应式状态 = 可以驱动重新渲染的状态(如 useState)
理解了这一点,就能避免很多初学者常见的 useEffect 不执行、副作用失效的问题,也能更清晰地区分 React 与 Vue 在响应式系统上的本质差异。
评论