Join the discussion
Comments
Loading comments...
Cookie preferences
We use essential cookies for sign-in and preferences. With your permission, we also use basic analytics to improve lessons.
Join the discussion
Loading comments...
We use essential cookies for sign-in and preferences. With your permission, we also use basic analytics to improve lessons.
React 如何创建虚拟表示并进行差异比较以找到最小DOM变更。
虚拟 DOM 是一个轻量级的 JavaScript 对象树,映射真实 DOM。React 保留两份副本:当前树和每次渲染时构建的 work-in-progress 树。React 不会立即操作真实 DOM,而是对两棵虚拟树进行 diff,计算出所需的最小变更集,然后一次性批量应用。
当你编写 JSX 时,Babel(或 React 编译器)将其转换为 React.createElement(type, props, ...children) 调用。这些调用返回普通的 JavaScript 对象,称为 React 元素 — 而不是真实的 DOM 节点。
React.createElement() 返回一个嵌套的对象树,如 '{' type: 'div', props: '{' className: 'box', children: [...] '}' '}'。这就是虚拟 DOM — 描述 UI 外观的廉价内存表示。
当 state 或 props 发生变化时,React 再次调用组件函数,产生一个全新的虚拟 DOM 树来表示新的期望 UI。
React 的 diffing 算法(O(n) 启发式)同时遍历两棵树。它假设不同类型的元素产生不同的树,并使用 key prop 跨渲染匹配列表项。
Diff 之后,React 得到一个变更列表:更新这个 prop、插入这个节点、删除那个节点。这些变更在 commit 阶段应用到真实 DOM — 最小化回流和重绘。
React.createElement() 返回的普通 JS 对象。描述 type、props 和 children。不是真实的 DOM 节点。
React 比较两棵虚拟树的 O(n) 启发式算法。假设相同位置的元素是相同类型,除非 key 不同。
比较新旧虚拟树并确定所需最小 DOM 操作集的过程。
列表项的稳定标识。让 React 即使在位置改变的情况下也能匹配新旧列表元素。
React 收集协调过程中所有的 DOM 变更并一次性应用,减少浏览器回流。
1// JSX you write:2const element = <div className="box"><span>Hello</span></div>;34// Compiles to:5const element = React.createElement(6 'div',7 { className: 'box' },8 React.createElement('span', null, 'Hello')9);1011// Produces this Virtual DOM object:12{13 type: 'div',14 props: {15 className: 'box',16 children: {17 type: 'span',18 props: { children: 'Hello' }19 }20 }21}
直接操作 DOM 代价高昂,因为浏览器必须重新计算布局并重绘。通过在 JavaScript 中进行 diff(快速)并批量执行 DOM 写入(慢速),React 将昂贵浏览器操作的次数降至最低。这就是为什么即使面对复杂 UI,React 应用也感觉流畅的原因。
You have a chat app rendering 500+ messages. Scrolling to load older messages causes the entire message list to re-render, taking 300ms and causing visible lag.
When new messages are appended, React creates a new VDOM tree for the entire list. The diffing algorithm walks all 500+ nodes even though only 20 new messages were added.
Use windowing (react-window/react-virtualized) to only render visible messages in the VDOM. Combined with proper `key` props, React's diff only processes ~20 visible nodes instead of 500+. The VDOM tree stays small, diffs stay fast.
Takeaway: The VDOM's diffing cost is proportional to tree size, not DOM size. Keeping your VDOM tree small via virtualization is the most effective optimization for large lists.
A component receives `style={{ color: 'red' }}` as a prop. Despite no visual change, React DevTools shows it re-renders on every parent update.
Each render creates a NEW style object `{ color: 'red' }`. When React diffs oldProps vs newProps, `oldStyle !== newStyle` (different object reference), so React marks this node as 'changed' and commits a DOM update — even though the actual CSS hasn't changed.
Hoist the style object outside the component or wrap it in `useMemo`. Now `oldStyle === newStyle` (same reference), the diff correctly marks it as 'unchanged', and React skips the DOM write entirely.
Takeaway: Understanding how the VDOM diff compares props (by reference, not deep equality) reveals why inline objects, arrays, and arrow functions in JSX cause performance issues.