在 React 的协调(Reconciliation)过程中,Diff 算法需要根据新旧虚拟 DOM 节点的类型来决定如何处理。不同类型的节点,其比较和更新策略完全不同。React 将虚拟 DOM 节点主要分为三类:元素节点、组件节点和文本节点,在 Diff 时首先判断节点类型,再进入对应的处理分支。
1. 元素节点
元素节点对应原生 DOM 元素,如 <div>、<span>、<input> 等。在虚拟 DOM 对象中,它们的 type 字段是一个字符串(如 'div')。
判断方式:当新旧节点的 type 都是字符串且相等时,React 认为它们是同一类型的 DOM 元素,会复用底层 DOM 节点,仅更新变化的属性(props)和子节点。
如果 type 不同(例如从 <div> 变成 <span>),React 会认为整棵子树需要重建,直接销毁旧节点及其子节点,创建全新的 DOM 节点。
示例:
// 从
<div className="old">Hello</div>
// 变为
<div className="new">World</div>
比较过程:
- 发现都是
'div',类型一致,复用 DOM 节点。 - 对比 props,更新
className属性。 - 对比子节点文本,从
'Hello'更新为'World'。
// 从
<div>Hello</div>
// 变为
<span>Hello</span>
比较过程:
div与span类型不同,直接卸载整个<div>及其子树,创建新的<span>并挂载。
2. 组件节点
组件节点对应自定义的 React 组件(函数组件或类组件)。在虚拟 DOM 对象中,type 字段是一个函数(函数组件)或类(类组件),而不是字符串。
判断方式:当新旧节点的 type 引用同一个组件构造函数(或函数)时,React 认为组件类型相同,会复用组件实例(类组件)或重新执行组件函数(函数组件)。如果组件类型不同,则卸载旧组件,挂载新组件。
组件节点的 Diff 过程是递归的:
- 确定组件类型后,更新组件的 props。
- 触发组件渲染,得到组件输出的子虚拟 DOM 树。
- 对子虚拟 DOM 树继续执行 Diff。
示例:
// 旧树
<MyComponent name="Alice" />
// 新树
<MyComponent name="Bob" />
比较过程:
- 两处
type都指向同一个MyComponent函数,类型相同。 - 更新 props 为
{ name: 'Bob' },触发MyComponent重新渲染。 - 对其内部返回的元素继续 Diff。
如果换了组件类型:
// 从
<MyComponent />
// 变为
<OtherComponent />
React 会彻底卸载 MyComponent(包括其使用到的 effects、state 等),并挂载新的 OtherComponent,整个子树重建。
3. 文本节点
文本节点对应虚拟 DOM 中的纯文本内容,其虚拟 DOM 对象不再有 type 字段,而是一个字符串或数字。
判断方式:当新旧节点都是文本节点(都是基本类型)时,React 直接比较字符串内容。如果内容相同,不做任何操作;如果内容不同,直接更新真实 DOM 的 textContent。
如果一侧是文本节点,另一侧是元素节点或组件节点,则 React 会销毁旧节点,创建新类型的节点。例如从 Hello 变成 <span>Hello</span>,文本节点和元素节点类型不同,直接替换。
示例:
// 旧树
<div>Hello</div>
// 新树
<div>World</div>
当处理 <div> 的子节点时:
- 旧子节点为文本
'Hello',新子节点为文本'World'。 - 两者都是文本节点,直接比较更新真实 DOM 的文本内容。
4. 节点类型判断的核心逻辑
React 在协调过程中的起点是 reconcileChildrenArray 或 reconcileSingleElement 等方法,它们首先获取 element.type 并进行判断:
function isSameType(newChild, oldFiber) {
// 元素节点:typeof type === 'string'
// 组件节点:typeof type === 'function'
// 文本节点:无 type,通过对象结构区分
if (typeof newChild === 'object' && newChild !== null) {
return newChild.type === oldFiber.type;
}
// 文本节点
return typeof newChild === 'string' || typeof newChild === 'number';
}
这种先判断类型再决定更新策略的方式,是 React Diff 算法高效的基础。理解它的行为有助于避免常见错误,比如:
- 在列表中随意改变组件类型会导致子组件 完全销毁重建,丢失状态且产生不必要的性能开销。
- 在 JSX 中混用文本和元素时,注意 React 如何处理相邻节点的 Diff。
掌握节点类型的判断逻辑,你就能更精准地预测 React 更新 DOM 的行为,并写出更高效的组件代码。