838 字
4 分钟
虚拟 DOM 与 React 元素

React 元素是普通对象#

const element = <h1 title="foo">你好</h1>

官方在 Writing Markup with JSX 那页说得很直接:JSX 看起来像 HTML,但在底层会被转换成普通的 JavaScript 对象。useMemo 参考页里给过这个对象的形状:

{ type: List, props: { items: visibleTodos } }

type 是元素类型,props 是属性与子节点。它没有方法,也不指向任何 DOM 节点。这棵由对象组成的树,社区叫它虚拟 DOM 树。

官方讲的是三步,不是「算法」#

Render and Commit 把一次界面更新拆成三步:

  1. 触发。初次渲染调用 root.render();之后的渲染由某处 state 更新排入。
  2. 渲染。这一步是 React 调用你的组件。初次渲染从根组件开始;后续渲染从「state 变了的那一个组件」开始,再递归到它返回的子组件。
  3. 提交。React 改 DOM。初次渲染用 appendChild 把节点放上屏幕;重渲染时「应用必要的最小改动,这些改动是渲染期间算出来的」。

官方还有一句最该记住的:两次渲染结果相同,React 就不碰 DOM。

它举的例子是一个每秒重渲染一次的组件,里面有个 <input>。你往输入框里打的字不会消失,因为 React 看到 input 还在原来的位置,就没动它,也没动它的 value。

那「比较」按什么规则#

现行文档把这件事讲成位置与身份。Preserving and Resetting State 说 React 按组件在树里的位置判断身份:同一个位置上的同一个组件保留 state,key 就是用来改变这个判断的。

至于「不同类型产出不同树、同类型只更新属性、按 key 匹配子节点」这套启发式规则,只存在于已归档的旧站文档:Reconciliation。那页顶部挂着「This site is no longer updated」的横幅,官方把它指向上面那篇。要引它就注明已归档,别当成现行规范。

用代码看一眼元素长什么样#

const element = React.createElement(
'div',
{ id: 'box' },
React.createElement('span', null, '你好')
)

createElement 现在被官方归在 Legacy API 里,日常写 JSX 不用直接调它,工具链会生成等价的调用。上面的写法与 React.createElement('div', { id: 'box' }, React.createElement('span', null, '你好')) 得到的对象,和 Babel 或 SWC 从 JSX 生成的是一回事,细节见本系列的 Babel 与 SWC。

跨平台那件事#

元素只是描述,把它变成什么由渲染器决定:react-dom 产出 DOM 节点,React Native 产出原生视图。这是抽象层带来的收益,与性能无关。

三个容易被讲糊的说法#

「虚拟 DOM 比直接操作 DOM 快」不准确。官方的口径是重渲染时只应用必要的最小改动,而不是每次重建整棵 DOM。在很简单的场景里,手写 DOM 操作可能更快。

「有虚拟 DOM 才能跨平台」也不严谨。让 React Native 成立的是组件模型与渲染器的分离,虚拟 DOM 只是这条路上的一个中间产物。

「diff 是 O(n) 算法」出自那页已归档的文档,它依赖两条假设:不同类型的元素产出不同的树,开发者用 key 提示哪些子节点是稳定的。官方现在不再讲这套,引它可以,但要说清来源。

第 35 篇,共 39 篇本文属于系列

虚拟 DOM 与 React 元素
https://www.choria.top/posts/react-vdom/
作者
乔瑞雅·克林威尔
发布于
2026-04-30
许可协议
CC BY-NC-SA 4.0

分享文章

生成精美分享图或复制链接,与更多人分享本文。

继续阅读

沿着主题读

基于共同的标签与分类

换条路线

从其他文章中稳定抽取

最后更新于 ,距今已过 192 天

部分内容可能已过时