跳至正文

🎨 什么是 Virtual DOM?

📖 定义

Virtual DOM 是真实 DOM 的轻量级副本,是一个只存在于内存中的 JavaScript 对象。像 React 这样的库使用 Virtual DOM 先在虚拟环境中处理 UI 变化,然后只将最小的变更应用到真实 DOM。这大大提高了 Web 应用程序的性能。

🎯 通过比喻理解

建筑蓝图比喻

将 Virtual DOM 比喻为建筑蓝图:

真实建筑 (Real DOM)
├─ 建筑成本高
├─ 修改耗时长
└─ 拆除重建困难

蓝图 (Virtual DOM)
├─ 可在纸上自由修改
├─ 快速且便宜
├─ 可比较多个方案
└─ 只将最终方案应用到真实建筑

流程:
1. 在蓝图上多次修改 (Virtual DOM)
2. 识别变更部分
3. 只对真实建筑应用最小变更 (Real DOM)

这样可以节省建筑成本和时间!

文字处理器比喻

记事本 (原生 JavaScript)
- 每次输入都立即保存
- 缓慢且低效
- 重复文件 I/O

文字处理器 (React + Virtual DOM)
- 在内存中编辑
- 跟踪变更
- 只在按保存按钮时写入文件
- 快速且高效

Virtual DOM = 内存缓冲区
Real DOM = 保存到磁盘

游戏渲染比喻

在游戏开发中:

直接绘制到屏幕 (直接操作 Real DOM)
- 会出现闪烁
- 性能下降
- 难以部分更新

双缓冲 (Virtual DOM 方式)
1. 在后台屏幕(缓冲区)绘制
2. 完成后与前台屏幕交换
3. 流畅渲染
4. 只更新变更的像素

Virtual DOM 起到与双缓冲相同的作用!

⚙️ 工作原理

1. 什么是 DOM?

// DOM (文档对象模型)
// 允许用 JavaScript 操作 HTML 的接口

<!DOCTYPE html>
<html>
<body>
<div id="root">
<h1>Hello</h1>
<p>World</p>
</div>
</body>
</html>

// DOM 树
document
└─ html
└─ body
└─ div#root
├─ h1 ("Hello")
└─ p ("World")

// 用 JavaScript 操作 DOM
document.getElementById('root').innerHTML = '<h1>Changed!</h1>';

2. Real DOM 的问题

// ❌ 低效的 Real DOM 操作

// 问题 1: 每次都完全重新渲染
for (let i = 0; i < 1000; i++) {
const div = document.createElement('div');
div.textContent = `Item ${i}`;
document.body.appendChild(div); // 1000次 DOM 操作!
}

// 问题 2: 布局重新计算 (Reflow)
element.style.width = '100px'; // 发生 Reflow
element.style.height = '100px'; // 发生 Reflow
element.style.margin = '10px'; // 发生 Reflow

// 问题 3: 复杂的 UI 更新
// 即使只改变一个字符也要重新创建整个组件
<div>
<Header />
<Content /> ← 只有这个改变了
<Footer />
</div>
// 必须全部重新创建!

// Real DOM 操作慢的原因:
// 1. 浏览器渲染引擎和 JavaScript 引擎之间的通信
// 2. Reflow (布局重新计算)
// 3. Repaint (重新绘制屏幕)

3. Virtual DOM 操作流程

// ✅ Virtual DOM 的高效处理

// 步骤 1: 初始渲染
const initialVirtualDOM = {
type: 'div',
props: { id: 'app' },
children: [
{ type: 'h1', props: {}, children: ['Count: 0'] },
{ type: 'button', props: {}, children: ['Click'] }
]
};

// 反映到 Real DOM
<div id="app">
<h1>Count: 0</h1>
<button>Click</button>
</div>

// 步骤 2: 状态改变 (count = 1)
const newVirtualDOM = {
type: 'div',
props: { id: 'app' },
children: [
{ type: 'h1', props: {}, children: ['Count: 1'] }, // ← 已改变
{ type: 'button', props: {}, children: ['Click'] }
]
};

// 步骤 3: Diffing (计算差异)
const diff = {
path: ['div', 'h1', 'text'],
oldValue: 'Count: 0',
newValue: 'Count: 1'
};

// 步骤 4: Reconciliation (协调)
// 只将最小变更应用到 Real DOM
document.querySelector('h1').textContent = 'Count: 1';
// div 和 button 保持不变!

4. Diffing 算法

// React 的 Diffing 算法原理

// 规则 1: 不同类型的元素产生不同的树
// 之前
<div>
<Counter />
</div>

// 之后
<span>
<Counter />
</span>

// 结果: 完全替换 div 为 span
// Counter 也会卸载后重新挂载

// 规则 2: 使用 key 追踪列表项
// ❌ 没有 key
<ul>
<li>A</li>
<li>B</li>
</ul>

// 在开头添加 C
<ul>
<li>C</li> // 重新创建全部
<li>A</li>
<li>B</li>
</ul>

// ✅ 使用 key
<ul>
<li key="a">A</li>
<li key="b">B</li>
</ul>

// 在开头添加 C
<ul>
<li key="c">C</li> // 只添加 C
<li key="a">A</li> // 保持不变
<li key="b">B</li> // 保持不变
</ul>

// 规则 3: 递归比较子元素
function diff(oldNode, newNode) {
// 1. 类型不同则替换
if (oldNode.type !== newNode.type) {
return { action: 'REPLACE', newNode };
}

// 2. 比较属性
const propsDiff = diffProps(oldNode.props, newNode.props);

// 3. 比较子元素 (递归)
const childrenDiff = diffChildren(
oldNode.children,
newNode.children
);

return { propsDiff, childrenDiff };
}

5. Reconciliation (协调)

// Reconciliation: 将 Virtual DOM 反映到 Real DOM

// 阶段 1: Render Phase (异步)
// - 比较 Virtual DOM
// - 计算变更
// - 可中断 (React 18 Fiber)

// 阶段 2: Commit Phase (同步)
// - 更新 Real DOM
// - 执行生命周期方法
// - 不可中断

// 示例: 列表更新
const oldList = [
{ id: 1, name: 'A' },
{ id: 2, name: 'B' },
{ id: 3, name: 'C' }
];

const newList = [
{ id: 1, name: 'A' },
{ id: 3, name: 'C-updated' }, // 修改
{ id: 4, name: 'D' } // 添加
// id: 2 删除
];

// Reconciliation 结果:
// - id:1 保留
// - id:2 移除
// - id:3 只更新文本
// - id:4 添加

// 最小化实际 DOM 操作:
// remove(id:2)
// updateText(id:3, 'C-updated')
// append(id:4)

💡 实际示例

基本 Virtual DOM 实现

// 简单的 Virtual DOM 实现 (教学用)

// 1. 创建 Virtual DOM 节点
function createElement(type, props = {}, ...children) {
return {
type,
props,
children: children.flat()
};
}

// 不使用 JSX
const vdom = createElement(
'div',
{ id: 'app', className: 'container' },
createElement('h1', {}, 'Hello Virtual DOM'),
createElement('p', {}, 'This is a paragraph')
);

console.log(vdom);
// {
// type: 'div',
// props: { id: 'app', className: 'container' },
// children: [
// { type: 'h1', props: {}, children: ['Hello Virtual DOM'] },
// { type: 'p', props: {}, children: ['This is a paragraph'] }
// ]
// }

// 2. 将 Virtual DOM 转换为 Real DOM
function render(vnode) {
// 文本节点
if (typeof vnode === 'string' || typeof vnode === 'number') {
return document.createTextNode(vnode);
}

// 元素节点
const element = document.createElement(vnode.type);

// 应用属性
Object.entries(vnode.props || {}).forEach(([key, value]) => {
if (key === 'className') {
element.setAttribute('class', value);
} else if (key.startsWith('on')) {
// 事件监听器
const event = key.substring(2).toLowerCase();
element.addEventListener(event, value);
} else {
element.setAttribute(key, value);
}
});

// 渲染子元素 (递归)
(vnode.children || []).forEach(child => {
element.appendChild(render(child));
});

return element;
}

// 使用
const domElement = render(vdom);
document.body.appendChild(domElement);

Diffing 算法实现

// 3. Diffing 算法 (简化版)
function diff(oldVNode, newVNode) {
// 1. 如果新节点不存在则移除
if (!newVNode) {
return { type: 'REMOVE' };
}

// 2. 如果旧节点不存在则添加
if (!oldVNode) {
return { type: 'CREATE', newVNode };
}

// 3. 如果类型不同则替换
if (
typeof oldVNode !== typeof newVNode ||
(typeof oldVNode === 'string' && oldVNode !== newVNode) ||
oldVNode.type !== newVNode.type
) {
return { type: 'REPLACE', newVNode };
}

// 4. 如果是相同类型则更新
if (newVNode.type) {
return {
type: 'UPDATE',
props: diffProps(oldVNode.props, newVNode.props),
children: diffChildren(oldVNode.children, newVNode.children)
};
}

// 5. 无变化
return { type: 'NONE' };
}

// 比较属性
function diffProps(oldProps = {}, newProps = {}) {
const patches = [];

// 已更改或添加的属性
Object.keys(newProps).forEach(key => {
if (oldProps[key] !== newProps[key]) {
patches.push({ type: 'SET_PROP', key, value: newProps[key] });
}
});

// 已移除的属性
Object.keys(oldProps).forEach(key => {
if (!(key in newProps)) {
patches.push({ type: 'REMOVE_PROP', key });
}
});

return patches;
}

// 比较子元素
function diffChildren(oldChildren = [], newChildren = []) {
const patches = [];
const maxLength = Math.max(oldChildren.length, newChildren.length);

for (let i = 0; i < maxLength; i++) {
patches.push(diff(oldChildren[i], newChildren[i]));
}

return patches;
}

应用补丁

// 4. 将补丁应用到 Real DOM
function patch(parent, patches, index = 0) {
if (!patches) return;

const element = parent.childNodes[index];

switch (patches.type) {
case 'CREATE':
parent.appendChild(render(patches.newVNode));
break;

case 'REMOVE':
parent.removeChild(element);
break;

case 'REPLACE':
parent.replaceChild(render(patches.newVNode), element);
break;

case 'UPDATE':
// 更新属性
patches.props.forEach(propPatch => {
if (propPatch.type === 'SET_PROP') {
element.setAttribute(propPatch.key, propPatch.value);
} else if (propPatch.type === 'REMOVE_PROP') {
element.removeAttribute(propPatch.key);
}
});

// 更新子元素 (递归)
patches.children.forEach((childPatch, i) => {
patch(element, childPatch, i);
});
break;
}
}

在 React 中使用 Virtual DOM

import React, { useState } from 'react';

function Counter() {
const [count, setCount] = useState(0);

// React 自动处理 Virtual DOM
return (
<div className="counter">
<h1>Count: {count}</h1>
<button onClick={() => setCount(count + 1)}>
Increment
</button>
<button onClick={() => setCount(count - 1)}>
Decrement
</button>
</div>
);
}

// 内部操作:
// 1. count 改变 → 调用 setState
// 2. React 创建新的 Virtual DOM
// 3. 与之前的 Virtual DOM 比较 (Diffing)
// 4. 检测到只有 <h1> 文本改变
// 5. 只更新 Real DOM 的文本节点
// 6. div 和 button 保持不变

// 性能比较:
// ❌ 原生 JS: 替换整个 div.innerHTML
// ✅ React: 只更新 <h1> 的文本

复杂列表示例

import React, { useState } from 'react';

function TodoList() {
const [todos, setTodos] = useState([
{ id: 1, text: '运动', done: false },
{ id: 2, text: '学习', done: false },
{ id: 3, text: '打扫', done: false }
]);

const toggleTodo = (id) => {
setTodos(todos.map(todo =>
todo.id === id ? { ...todo, done: !todo.done } : todo
));
};

const deleteTodo = (id) => {
setTodos(todos.filter(todo => todo.id !== id));
};

return (
<div>
<h1>待办事项列表</h1>
<ul>
{todos.map(todo => (
<li
key={todo.id} // key 对于 Diffing 优化至关重要!
style={{
textDecoration: todo.done ? 'line-through' : 'none',
color: todo.done ? '#888' : '#000'
}}
>
<input
type="checkbox"
checked={todo.done}
onChange={() => toggleTodo(todo.id)}
/>
<span>{todo.text}</span>
<button onClick={() => deleteTodo(todo.id)}>删除</button>
</li>
))}
</ul>
</div>
);
}

// Virtual DOM 的效率:
// 即使只勾选一个 todo
// - ❌ 重新创建整个列表 (X)
// - ✅ 只改变该 <li> 的 style (O)

// key 的重要性:
// 有 key 时:
// - 移动/删除项目时高效
// - 可重用 DOM
// - 保留组件状态

// 没有 key 时:
// - 重新创建整个列表
// - 性能下降
// - 可能丢失组件状态

性能优化示例

import React, { useState, useMemo, memo } from 'react';

// 使用 memo 进行组件记忆化
const TodoItem = memo(function TodoItem({ todo, onToggle, onDelete }) {
console.log(`TodoItem ${todo.id} 渲染`);

return (
<li>
<input
type="checkbox"
checked={todo.done}
onChange={() => onToggle(todo.id)}
/>
<span>{todo.text}</span>
<button onClick={() => onDelete(todo.id)}>删除</button>
</li>
);
});

function OptimizedTodoList() {
const [todos, setTodos] = useState([
{ id: 1, text: '运动', done: false },
{ id: 2, text: '学习', done: false },
{ id: 3, text: '打扫', done: false }
]);

// 使用 useMemo 缓存计算结果
const activeTodos = useMemo(() => {
console.log('计算活动待办事项');
return todos.filter(todo => !todo.done);
}, [todos]);

const toggleTodo = (id) => {
setTodos(todos.map(todo =>
todo.id === id ? { ...todo, done: !todo.done } : todo
));
};

const deleteTodo = (id) => {
setTodos(todos.filter(todo => todo.id !== id));
};

return (
<div>
<h1>待办事项列表</h1>
<p>剩余待办: {activeTodos.length}</p>
<ul>
{todos.map(todo => (
<TodoItem
key={todo.id}
todo={todo}
onToggle={toggleTodo}
onDelete={deleteTodo}
/>
))}
</ul>
</div>
);
}

// 优化效果:
// 1. memo: 当一个 todo 改变时,只有该 TodoItem 重新渲染
// 2. useMemo: 只在 todos 改变时重新计算 activeTodos
// 3. key: 高效的列表更新

// 性能比较:
// 优化前: 10个 todo 中改变 1个 → 全部 10个重新渲染
// 优化后: 10个 todo 中改变 1个 → 只有 1个重新渲染

有无 Virtual DOM 的比较

// ❌ 没有 Virtual DOM (原生 JavaScript)
function updateWithoutVirtualDOM() {
const app = document.getElementById('app');
const count = 0;

// 每次重新创建整个 HTML
setInterval(() => {
count++;
app.innerHTML = `
<div class="container">
<h1>Count: ${count}</h1>
<p>Current time: ${new Date().toLocaleTimeString()}</p>
<button>Click me</button>
</div>
`;
// 问题:
// 1. 重新创建整个 DOM 树
// 2. 丢失事件监听器
// 3. 正在输入的 input 被重置
// 4. 滚动位置重置
// 5. 动画中断
}, 1000);
}

// ✅ 有 Virtual DOM (React)
function CounterWithVirtualDOM() {
const [count, setCount] = useState(0);
const [time, setTime] = useState(new Date());

useEffect(() => {
const timer = setInterval(() => {
setCount(c => c + 1);
setTime(new Date());
}, 1000);

return () => clearInterval(timer);
}, []);

return (
<div className="container">
<h1>Count: {count}</h1>
<p>Current time: {time.toLocaleTimeString()}</p>
<button onClick={() => alert('Clicked!')}>Click me</button>
</div>
);
}

// 优点:
// 1. 只更新 <h1> 和 <p> 的文本
// 2. 保留事件监听器
// 3. 保留组件状态
// 4. 流畅更新
// 5. 最小化 DOM 操作

实际性能测量

import React, { useState, useEffect } from 'react';

function PerformanceComparison() {
const [items, setItems] = useState([]);
const [renderTime, setRenderTime] = useState(0);

const generateItems = (count) => {
const startTime = performance.now();

const newItems = Array.from({ length: count }, (_, i) => ({
id: i,
value: Math.random()
}));

setItems(newItems);

const endTime = performance.now();
setRenderTime(endTime - startTime);
};

return (
<div>
<h1>性能比较</h1>
<div>
<button onClick={() => generateItems(100)}>
生成 100 个
</button>
<button onClick={() => generateItems(1000)}>
生成 1000 个
</button>
<button onClick={() => generateItems(10000)}>
生成 10000 个
</button>
</div>

<p>渲染时间: {renderTime.toFixed(2)}ms</p>

<div style={{ maxHeight: '400px', overflow: 'auto' }}>
{items.map(item => (
<div key={item.id} style={{ padding: '5px', borderBottom: '1px solid #eee' }}>
Item #{item.id}: {item.value.toFixed(4)}
</div>
))}
</div>
</div>
);
}

// 结果 (典型情况):
// 原生 JS (整个 innerHTML):
// - 100 个: ~50ms
// - 1000 个: ~500ms
// - 10000 个: ~5000ms (卡顿)

// React (Virtual DOM):
// - 100 个: ~10ms
// - 1000 个: ~100ms
// - 10000 个: ~1000ms (流畅)

// Virtual DOM 的性能提升:
// 初始渲染类似,
// 但更新时快 5-10 倍!

使用 Chrome DevTools 检查

// React DevTools Profiler 使用方法

// 1. 安装 React DevTools
// 从 Chrome 网上应用店安装 "React Developer Tools"

// 2. 打开 Profiler 标签
// - 点击录制按钮
// - 在应用中执行操作
// - 停止录制

// 3. 分析结果
// - Flamegraph: 组件渲染时间
// - Ranked: 渲染时间排名
// - 点击组件: 查看为什么渲染

function ProfiledComponent() {
const [count, setCount] = useState(0);

// 用 Profiler 包裹以测量渲染性能
return (
<React.Profiler
id="Counter"
onRender={(id, phase, actualDuration) => {
console.log(`${id} (${phase}) took ${actualDuration}ms`);
}}
>
<div>
<h1>Count: {count}</h1>
<button onClick={() => setCount(count + 1)}>
Increment
</button>
</div>
</React.Profiler>
);
}

// 控制台输出:
// Counter (mount) took 2.3ms
// Counter (update) took 0.5ms ← 多亏了 Virtual DOM 很快!

🤔 常见问题

Q1. Virtual DOM 总是比 Real DOM 快吗?

A: 不,这取决于情况:

// ✅ Virtual DOM 更快的情况:
// 1. 复杂的 UI 更新
function ComplexUI() {
const [data, setData] = useState([...]);

// 许多组件中只有一些改变
return (
<div>
{data.map(item => <ComplexComponent key={item.id} {...item} />)}
</div>
);
// Virtual DOM: 只更新改变的部分
// Real DOM: 需要重新创建全部
}

// 2. 频繁更新
function LiveCounter() {
const [count, setCount] = useState(0);

useEffect(() => {
const timer = setInterval(() => {
setCount(c => c + 1);
}, 100); // 每秒 10 次更新
return () => clearInterval(timer);
}, []);

return <h1>Count: {count}</h1>;
// Virtual DOM: 只改变文本
// Real DOM: 每次都要操作 DOM
}

// ❌ Virtual DOM 更慢的情况:
// 1. 简单的 UI (开销)
function SimpleButton() {
return <button>Click</button>;
}
// Virtual DOM 比较 + Real DOM 更新
// vs
// 直接操作 Real DOM

// 2. 初始渲染
// Virtual DOM 创建 + Diffing + Real DOM 创建
// vs
// 直接创建 Real DOM

// 结论:
// Virtual DOM 的优势在于"更新性能"
// 初始加载可能稍慢,
// 但对于复杂应用整体更快!

Q2. 为什么 key 很重要?

A: key 用于 React 识别哪些项目已改变/添加/删除:

// ❌ 没有 key (低效)
function BadList() {
const [items, setItems] = useState(['A', 'B', 'C']);

return (
<ul>
{items.map(item => <li>{item}</li>)}
</ul>
);
}

// 在开头添加 'D' 时:
// 之前: <li>A</li> <li>B</li> <li>C</li>
// 之后: <li>D</li> <li>A</li> <li>B</li> <li>C</li>

// 没有 key 时,React:
// - 第 0 个: A → D (改变)
// - 第 1 个: B → A (改变)
// - 第 2 个: C → B (改变)
// - 第 3 个: 无 → C (添加)
// 结果: 全部 4 个都更新! (低效)

// ✅ 有 key (高效)
function GoodList() {
const [items, setItems] = useState([
{ id: 'a', text: 'A' },
{ id: 'b', text: 'B' },
{ id: 'c', text: 'C' }
]);

return (
<ul>
{items.map(item => (
<li key={item.id}>{item.text}</li>
))}
</ul>
);
}

// 在开头添加 'D' 时:
// 之前: key=a, key=b, key=c
// 之后: key=d, key=a, key=b, key=c

// React 通过 key 识别:
// - key=a: 保持不变 (保留)
// - key=b: 保持不变 (保留)
// - key=c: 保持不变 (保留)
// - key=d: 新添加
// 结果: 只添加 1 个! (高效)

// key 规则:
// 1. 在兄弟之间必须唯一
// 2. 不能改变
// 3. 必须可预测

// ❌ 不好的 key 示例:
{items.map((item, index) => <li key={index}>{item}</li>)}
// 索引在顺序改变时会出问题!

{items.map(item => <li key={Math.random()}>{item}</li>)}
// 每次都是新 key → 总是重新创建!

// ✅ 好的 key 示例:
{items.map(item => <li key={item.id}>{item}</li>)}
// 使用数据的唯一 ID

{items.map(item => <li key={item.email}>{item}</li>)}
// 使用唯一属性

Q3. 可以直接操作 Virtual DOM 吗?

A: 不可以,React 会自动处理。但可以优化:

import React, { useState, useMemo, useCallback, memo } from 'react';

// 1. React.memo - 组件记忆化
const ExpensiveComponent = memo(function ExpensiveComponent({ value }) {
console.log('ExpensiveComponent 渲染');

// 复杂计算
const result = useMemo(() => {
let sum = 0;
for (let i = 0; i < 1000000; i++) {
sum += i;
}
return sum + value;
}, [value]);

return <div>Result: {result}</div>;
});

// 如果 props 相同则跳过重新渲染!

// 2. useMemo - 值记忆化
function ListComponent({ items }) {
// 只在 items 改变时重新计算
const sortedItems = useMemo(() => {
console.log('排序中...');
return [...items].sort((a, b) => a.value - b.value);
}, [items]);

return (
<ul>
{sortedItems.map(item => (
<li key={item.id}>{item.value}</li>
))}
</ul>
);
}

// 3. useCallback - 函数记忆化
function Parent() {
const [count, setCount] = useState(0);
const [other, setOther] = useState(0);

// 只在 count 改变时创建新函数
const handleClick = useCallback(() => {
setCount(c => c + 1);
}, []);

return (
<div>
<Child onClick={handleClick} />
<button onClick={() => setOther(other + 1)}>
Other: {other}
</button>
</div>
);
}

const Child = memo(function Child({ onClick }) {
console.log('Child 渲染');
return <button onClick={onClick}>Click</button>;
});

// other 改变时 Child 不重新渲染!

// 4. shouldComponentUpdate / PureComponent (类组件)
class OptimizedComponent extends React.PureComponent {
render() {
return <div>{this.props.value}</div>;
}
}

// 如果 props 没有改变则不重新渲染

// 5. 条件渲染优化
function ConditionalRendering({ showDetails, basicInfo, detailInfo }) {
return (
<div>
<BasicInfo data={basicInfo} />
{showDetails && <DetailInfo data={detailInfo} />}
</div>
);
}

// 如果 showDetails 为 false,DetailInfo 在 Virtual DOM 中也不存在!

Q4. 有没有不使用 Virtual DOM 的框架?

A: 有,像 Svelte 这样的框架使用不同的方法:

// React (使用 Virtual DOM)
function Counter() {
const [count, setCount] = useState(0);

return (
<div>
<h1>Count: {count}</h1>
<button onClick={() => setCount(count + 1)}>+</button>
</div>
);
}

// 运行时:
// 1. 创建 Virtual DOM
// 2. Diffing
// 3. 更新 Real DOM

// Svelte (编译时优化)
<script>
let count = 0;
</script>

<div>
<h1>Count: {count}</h1>
<button on:click={() => count += 1}>+</button>
</div>

// 编译结果:
function update_count() {
h1.textContent = `Count: ${count}`; // 直接操作 DOM!
}

// 优缺点比较:

// Virtual DOM (React, Vue)
// 优点:
// - 声明式 UI
// - 可预测的性能
// - 丰富的生态系统

// 缺点:
// - 内存开销
// - Diffing 成本
// - 包体积大

// No Virtual DOM (Svelte)
// 优点:
// - 快速性能
// - 包体积小
// - 内存使用低

// 缺点:
// - 生态系统小
// - 复杂应用如何?
// - 学习曲线

// Solid.js (细粒度响应式)
// 不使用 Virtual DOM 的 React 语法
import { createSignal } from 'solid-js';

function Counter() {
const [count, setCount] = createSignal(0);

return (
<div>
<h1>Count: {count()}</h1>
<button onClick={() => setCount(count() + 1)}>+</button>
</div>
);
}

// 精确更新只改变的部分!
// 即使没有 Virtual DOM 也很高效!

Q5. React 18 的并发渲染是什么?

A: 允许中断/恢复 Virtual DOM 更新的功能:

import React, { useState, useTransition, Suspense } from 'react';

// 1. useTransition - 低优先级更新
function SearchBox() {
const [query, setQuery] = useState('');
const [results, setResults] = useState([]);
const [isPending, startTransition] = useTransition();

const handleChange = (e) => {
// 立即更新 (高优先级)
setQuery(e.target.value);

// 稍后更新 (低优先级)
startTransition(() => {
// 繁重计算
const newResults = expensiveSearch(e.target.value);
setResults(newResults);
});
};

return (
<div>
<input
value={query}
onChange={handleChange}
placeholder="搜索..."
/>
{isPending && <span>搜索中...</span>}
<ul>
{results.map(result => (
<li key={result.id}>{result.title}</li>
))}
</ul>
</div>
);
}

// 行为:
// 1. 用户快速输入
// 2. query 立即更新 (保持输入响应性)
// 3. results 缓慢更新 (后台)
// 4. 如果有新输入,中断之前的 results 更新!

// 2. Suspense - 加载状态管理
const LazyComponent = React.lazy(() => import('./Heavy'));

function App() {
return (
<Suspense fallback={<div>加载中...</div>}>
<LazyComponent />
</Suspense>
);
}

// 3. useDeferredValue - 值延迟
function FilteredList({ items, query }) {
const deferredQuery = useDeferredValue(query);

const filteredItems = useMemo(() => {
return items.filter(item =>
item.name.includes(deferredQuery)
);
}, [items, deferredQuery]);

return (
<ul>
{filteredItems.map(item => (
<li key={item.id}>{item.name}</li>
))}
</ul>
);
}

// 并发渲染的优点:
// 1. 保持响应性: UI 不卡顿
// 2. 优先级: 重要更新优先
// 3. 可中断: 跳过不必要的工作
// 4. 平滑过渡: 防止突然变化

// 内部操作:
// Time Slicing:
// [输入] [渲染] [输入] [渲染] ...
// 将工作分成许多小块执行

// 优先级:
// Urgent (立即): 点击、输入、悬停
// Transition (稍后): 搜索结果、过滤
// Deferred (最后): 分析、日志

🎓 下一步

理解 Virtual DOM 后,尝试学习:

  1. React 是什么? (文档编写中) - 使用 Virtual DOM 的代表性库
  2. CSR vs SSR vs SSG - 渲染策略比较
  3. Web 性能优化 (文档编写中) - 使用 Virtual DOM 的优化

🎬 总结

Virtual DOM 是现代 Web 框架的核心技术:

  • 概念: Real DOM 的轻量级副本
  • 原理: 通过 Diffing 计算变更后进行最小更新
  • 优点: 提高复杂 UI 更新的性能
  • 核心: 通过声明式编程改善开发者体验

多亏了 Virtual DOM,我们可以专注于 UI 逻辑而无需担心性能!