5月28日 03:16
什么是 Virtual DOM?React 为什么用它替代直接操作 DOM?
Virtual DOM 是一棵用 JavaScript 对象描述的 DOM 树。React 状态变化时,先在内存里生成新的虚拟 DOM 树,和旧树做 diff,算出最小变更集,再批量更新真实 DOM。这么做的核心原因是——直接操作真实 DOM 太贵了,一次 appendChild 可能触发重排+重绘+合成三层渲染管线。Virtual DOM 把"手动精确定位变更"这件事自动化了:你只管声明 UI 长什么样,React 负责高效地同步到真实 DOM。顺带一提,正因为 UI 描述和渲染层解耦,React Native 才能复用同一套组件模型渲染原生控件。
追问
Virtual DOM 一定比直接操作 DOM 快吗?
不是。单改一个文本节点,el.textContent = 'xxx' 比 Virtual DOM diff 整棵子树更快。Virtual DOM 的价值在复杂 UI 场景:几十个组件同时更新时,它能自动算出最优更新路径,避免开发者手动 diff。所以它不是"最快",而是"在绝大多数场景下足够快,且不需要你操心"。
React 的 diff 算法怎么做到 O(n) 的?
朴素的树 diff 是 O(n³),React 用三个策略降到 O(n):
| 策略 | 含义 |
|---|---|
| 类型不同直接重建 | <div> 变成 <span>,整个子树丢弃重建,不跨类型 diff |
| key 标识稳定性 | 通过 key 追踪同一组子元素中的身份,避免错位复用 |
| 只比较同级 | 不跨层级比较,父亲和儿子不会互相匹配 |
代价是可能遗漏极少数跨层级移动的最优解,但实际场景中跨层级移动极少,这个权衡是值得的。
Vue 的 Virtual DOM 和 React 有什么区别?
Vue 的响应式系统精确追踪了"哪个组件依赖了哪个数据",数据变了可以直接跳过无关组件的 diff。React 默认从触发更新的组件开始整棵子树 diff,需要 React.memo、useMemo 手动优化。但 React 18 并发模式可以在调度层面拆分任务、让高优先级更新插队,这是 Vue 目前没有的。
实际项目里 Virtual DOM 有什么常见的坑?
- index 做 key:列表增删元素时 diff 错位,导致非预期复用和状态错乱。用唯一 ID 做 key。
- 大列表没有虚拟化:Virtual DOM 只解决 diff 效率,不解决渲染量。几千行的长列表必须上
react-window/react-virtuoso做虚拟滚动。 - 不必要的重渲染:父组件 state 变了,即使子组件 props 没变也会 diff。
React.memo和useMemo不是过早优化,是 React 开发的日常操作。