
react 并不会在每次状态更新时重新创建整个组件树的虚拟 dom;它仅对受状态变更影响的组件及其子树执行重渲染,并通过高效的 diff 算法比对新旧虚拟 dom 节点,实现最小化 dom 操作。
react 并不会在每次状态更新时重新创建整个组件树的虚拟 dom;它仅对受状态变更影响的组件及其子树执行重渲染,并通过高效的 diff 算法比对新旧虚拟 dom 节点,实现最小化 dom 操作。
在 React 中,“重渲染(re-render)”常被误解为“整棵树重建”。实际上,React 的渲染行为高度精细化且具备明确的边界控制能力。当某个组件(例如组件 D)内部调用 setState 或触发 useState 更新时,React 仅对该组件及其子组件(即其子树)触发函数执行或 render 方法调用,而兄弟节点(如 C 的其他子组件)、父组件(如 C、B、A)默认不会重新执行。
✅ 正确的渲染路径示例
假设组件层级为:
function A() { return <B />; }
function B() { return <C />; }
function C() { return <div><D /><OtherSibling /></div>; }
function D() {
const [count, setCount] = useState(0);
return <button onClick={() => setCount(c => c + 1)}>{count}</button>;
}当 D 内部 count 更新时:
- ✅ D 函数被重新调用,生成新的 React Element(即虚拟 DOM 节点)
- ✅ D 的直接子组件(如有)也会参与重渲染(深度优先)
- ❌ C、B、A 不会重新执行(除非它们自身依赖了该 state,或被显式 memo 失效)
- ❌ OtherSibling 完全不受影响,跳过渲染
? 技术本质:React 的协调器(Reconciler)以“fiber 节点”为单位构建工作单元。状态更新会标记对应 fiber 及其子 fiber 为 dirty,后续 render 阶段仅遍历这些 dirty 区域,而非全量遍历整棵树。
? Diffing 并非跨组件,而是基于 fiber 子树的局部比对
React 的 diff 算法(双端对比 + key 优化)作用范围是同一父节点下的子元素列表。例如在 D 返回的 JSX 中:
return (
<div>
<h2>Counter: {count}</h2>
<span className={count > 5 ? 'high' : 'normal'}>
{count}
</span>
</div>
);React 会将本次 D 生成的新 React.createElement 结果(即新的虚拟 DOM 片段),与其上一次输出进行逐层比对:
标签类型未变 → 复用 DOM 节点,仅更新文本内容
- 的 className 属性变化 → 标记 class 更新操作
- 整个 子节点结构未增删 → 无需移动或销毁节点
⚠️ 注意:diff 不跨层级比较(如不会把 D 的新输出和 OtherSibling 的旧输出做对比),它严格限定在当前组件返回的 JSX 结构内部。
? 关键保障机制:默认浅比较与可优化边界
- 函数组件无 memo 默认全量重渲染子树:若 C 未包裹 React.memo,即使 D 更新,C 本身仍会重新执行(但通常只返回相同 JSX,diff 后无真实 DOM 变更)。
-
使用 React.memo / useMemo / useCallback 可切断不必要的向下传播:
const D = React.memo(function D({ onIncrement }) { // ... });此时若 C 传给 D 的 props 未变,即使 C 重渲染,D 也不会触发二次渲染。
✅ 总结:React 渲染的三大核心原则
- 自顶向下标记,自底向上提交:状态更新触发对应 fiber 标记为 dirty,reconcile 阶段自上而下遍历 dirty 区域,commit 阶段自下而上应用 DOM 变更;
- 局部 diff,非全局重建:虚拟 DOM 重建与 diff 均限于变更组件及其子树,不涉及无关分支;
- 不可变更新驱动确定性:每次渲染都生成全新 React Element 对象,确保 diff 算法有可靠的新旧参照,这是高效更新的前提。
理解这一机制,不仅能写出更可控的性能代码,也是合理使用 memo、useMemo 和设计组件粒度的基础。










