Appearance
JSX与虚拟DOM关系
学习目标
通过本章学习,你将深入理解:
- 什么是虚拟DOM及其工作原理
- JSX如何转换为虚拟DOM
- React 19的编译优化机制
- 虚拟DOM与真实DOM的关系
- Diff算法的核心原理
- Fiber架构与虚拟DOM的关系
- 性能优化的底层原理
第一部分:虚拟DOM基础
1.1 什么是虚拟DOM
虚拟DOM(Virtual DOM)是一个JavaScript对象,它是真实DOM的轻量级副本。React使用虚拟DOM来提高性能,避免直接操作真实DOM带来的性能损耗。
真实DOM的问题
javascript
// 直接操作真实DOM(低效)
const element = document.createElement('div');
element.className = 'container';
element.innerHTML = '<p>Hello World</p>';
document.body.appendChild(element);
// 问题:
// 1. 每次操作都触发浏览器重排(reflow)和重绘(repaint)
// 2. DOM操作是同步的,阻塞JS执行
// 3. 大量DOM操作会导致性能问题虚拟DOM的优势
javascript
// 虚拟DOM(高效)
const vnode = {
type: 'div',
props: {
className: 'container',
children: [
{
type: 'p',
props: {
children: 'Hello World'
}
}
]
}
};
// 优势:
// 1. JavaScript对象操作比DOM操作快得多
// 2. 可以批量更新,减少DOM操作次数
// 3. 可以在内存中进行Diff计算
// 4. 支持跨平台渲染(React Native、服务端渲染)1.2 虚拟DOM的结构
基本结构
javascript
// 简单的虚拟DOM节点
const vnode = {
type: 'div', // 元素类型
props: { // 属性对象
className: 'box',
id: 'main',
children: 'Hello' // 子节点
},
key: null, // key标识
ref: null // ref引用
};复杂结构示例
javascript
// 嵌套的虚拟DOM树
const complexVNode = {
type: 'div',
props: {
className: 'app',
children: [
{
type: 'header',
props: {
children: {
type: 'h1',
props: {
children: '标题'
}
}
}
},
{
type: 'main',
props: {
children: [
{
type: 'p',
props: {
children: '段落1'
}
},
{
type: 'p',
props: {
children: '段落2'
}
}
]
}
}
]
}
};React元素对象
React通过React.createElement()创建的元素对象:
javascript
// React元素对象(React 18及之前)
const reactElement = {
$$typeof: Symbol.for('react.element'), // 标识这是React元素
type: 'div', // 元素类型
key: null, // key
ref: null, // ref
props: { // 属性
className: 'container',
children: 'Hello'
},
_owner: null, // 所有者组件
_store: {} // 内部存储
};
// React 19优化后的结构(更轻量)
const react19Element = {
$$typeof: Symbol.for('react.element'),
type: 'div',
props: {
className: 'container',
children: 'Hello'
},
key: null
// 移除了一些不必要的字段,减少内存占用
};1.3 虚拟DOM的工作流程
JSX代码
↓ (编译)
React.createElement() / jsx() (React 19新编译器)
↓ (创建)
虚拟DOM对象
↓ (Diff)
Diff算法计算差异
↓ (Patch)
更新真实DOM
↓
浏览器渲染详细流程
javascript
// 1. JSX代码
const App = () => (
<div className="app">
<h1>Hello World</h1>
</div>
);
// 2. 编译为函数调用(React 18)
const App = () =>
React.createElement(
'div',
{ className: 'app' },
React.createElement('h1', null, 'Hello World')
);
// 3. React 19新编译方式(更高效)
import { jsx } from 'react/jsx-runtime';
const App = () =>
jsx('div', {
className: 'app',
children: jsx('h1', {
children: 'Hello World'
})
});
// 4. 生成虚拟DOM对象
const vdom = {
$$typeof: Symbol.for('react.element'),
type: 'div',
props: {
className: 'app',
children: {
$$typeof: Symbol.for('react.element'),
type: 'h1',
props: {
children: 'Hello World'
}
}
}
};
// 5. ReactDOM渲染
ReactDOM.createRoot(document.getElementById('root')).render(<App />);
// 6. Fiber协调器处理虚拟DOM,生成Fiber树
// 7. Commit阶段更新真实DOM第二部分:JSX到虚拟DOM的转换
2.1 React 18的转换方式
createElement函数
javascript
// React 18及之前
React.createElement(type, props, ...children)
// 示例
React.createElement(
'div',
{ className: 'container', id: 'main' },
'Hello',
' ',
'World'
);
// 生成的对象
{
$$typeof: Symbol.for('react.element'),
type: 'div',
props: {
className: 'container',
id: 'main',
children: ['Hello', ' ', 'World']
},
key: null,
ref: null
}嵌套转换
jsx
// JSX
<div className="parent">
<span>Child 1</span>
<span>Child 2</span>
</div>
// 转换为
React.createElement(
'div',
{ className: 'parent' },
React.createElement('span', null, 'Child 1'),
React.createElement('span', null, 'Child 2')
);组件转换
jsx
// 函数组件
function Welcome({ name }) {
return <h1>Hello, {name}</h1>;
}
// JSX
<Welcome name="Alice" />
// 转换为
React.createElement(Welcome, { name: 'Alice' });
// 注意:type是组件函数本身,不是字符串
{
$$typeof: Symbol.for('react.element'),
type: Welcome, // 函数引用
props: { name: 'Alice' }
}2.2 React 19的新转换方式
React 19引入了新的JSX转换运行时,性能更高,体积更小。
jsx和jsxs函数
javascript
// React 19
import { jsx, jsxs } from 'react/jsx-runtime';
// 单个子节点使用jsx
jsx('div', {
className: 'container',
children: 'Hello'
});
// 多个子节点使用jsxs
jsxs('div', {
className: 'container',
children: [
jsx('span', { children: 'Child 1' }),
jsx('span', { children: 'Child 2' })
]
});新旧对比
javascript
// React 18方式
function Button({ text }) {
return React.createElement('button', null, text);
}
// React 19方式
import { jsx } from 'react/jsx-runtime';
function Button({ text }) {
return jsx('button', { children: text });
}
// 优势:
// 1. 不需要导入React
// 2. 更小的bundle体积
// 3. 更快的运行时性能
// 4. 自动优化children处理编译时优化
jsx
// JSX源码
function App() {
return (
<div className="app">
<h1>Title</h1>
<p>Content</p>
</div>
);
}
// React 19编译结果(优化后)
import { jsx, jsxs } from 'react/jsx-runtime';
function App() {
return jsxs('div', {
className: 'app',
children: [
jsx('h1', { children: 'Title' }),
jsx('p', { children: 'Content' })
]
});
}
// 编译器优化:
// - 自动导入jsx运行时
// - 区分单子节点(jsx)和多子节点(jsxs)
// - 优化children数组创建
// - 减少运行时开销2.3 特殊元素的转换
Fragment
jsx
// JSX
<>
<div>Item 1</div>
<div>Item 2</div>
</>
// React 19转换
import { Fragment } from 'react';
import { jsxs } from 'react/jsx-runtime';
jsxs(Fragment, {
children: [
jsx('div', { children: 'Item 1' }),
jsx('div', { children: 'Item 2' })
]
});表达式
jsx
// JSX
<div>{user.name}</div>
<div>{isLoggedIn ? 'Welcome' : 'Login'}</div>
<div>{items.map(item => <li key={item.id}>{item.text}</li>)}</div>
// 转换(表达式在运行时计算)
jsx('div', { children: user.name });
jsx('div', {
children: isLoggedIn ? 'Welcome' : 'Login'
});
jsx('div', {
children: items.map(item =>
jsx('li', {
children: item.text,
key: item.id
})
)
});属性展开
jsx
// JSX
<div {...props} className="custom" />
// 转换
jsx('div', {
...props,
className: 'custom'
});
// 注意:后面的属性会覆盖前面的第三部分:虚拟DOM的Diff算法
3.1 Diff算法的核心思想
React的Diff算法基于三个假设:
- 不同类型的元素会产生不同的树
- 开发者可以通过key来标识哪些子元素在不同渲染中保持稳定
- 只对同层级元素进行比较,不跨层级比较
传统Diff算法
javascript
// 传统Diff算法:O(n³) 复杂度
// 遍历旧树的每个节点,在新树中找到对应节点
// 计算最小编辑距离
// 对于1000个节点:
// 需要1000 × 1000 × 1000 = 10亿次比较React Diff算法
javascript
// React Diff:O(n) 复杂度
// 只比较同层级节点
// 通过key优化列表比较
// 对于1000个节点:
// 只需要1000次比较3.2 元素类型比较
不同类型的元素
jsx
// 旧虚拟DOM
<div>
<Counter />
</div>
// 新虚拟DOM
<span>
<Counter />
</span>
// Diff结果:
// 1. 销毁旧的div及其所有子节点
// 2. 创建新的span及其所有子节点
// 3. Counter组件会被完全卸载和重新挂载(状态丢失)相同类型的DOM元素
jsx
// 旧虚拟DOM
<div className="old" title="Old Title">
Content
</div>
// 新虚拟DOM
<div className="new" title="New Title">
Content
</div>
// Diff结果:
// 1. 保留div元素
// 2. 只更新className和title属性
// 3. 不重新创建DOM节点相同类型的组件元素
jsx
// 旧虚拟DOM
<User name="Alice" age={25} />
// 新虚拟DOM
<User name="Alice" age={26} />
// Diff结果:
// 1. 组件实例保持不变
// 2. 更新props
// 3. 触发组件的更新生命周期/重新渲染
// 4. 递归处理组件返回的虚拟DOM3.3 列表的Diff算法
无key的列表(效率低)
jsx
// 旧列表
<ul>
<li>A</li>
<li>B</li>
</ul>
// 新列表(在开头插入)
<ul>
<li>C</li>
<li>A</li>
<li>B</li>
</ul>
// 无key的Diff:
// 1. 将第一个<li>从A改为C
// 2. 将第二个<li>从B改为A
// 3. 在末尾插入新的<li>B</li>
// 效率低:修改了所有节点有key的列表(高效)
jsx
// 旧列表
<ul>
<li key="a">A</li>
<li key="b">B</li>
</ul>
// 新列表
<ul>
<li key="c">C</li>
<li key="a">A</li>
<li key="b">B</li>
</ul>
// 有key的Diff:
// 1. 识别出key="a"和key="b"的节点没变
// 2. 只在开头插入key="c"的新节点
// 3. 移动key="a"和key="b"的节点位置
// 效率高:只插入了一个节点Diff算法实现(简化版)
javascript
function diffChildren(oldChildren, newChildren) {
const oldMap = new Map();
const patches = [];
// 建立旧子节点的key映射
oldChildren.forEach((child, index) => {
const key = child.key || index;
oldMap.set(key, { child, index });
});
// 遍历新子节点
newChildren.forEach((newChild, newIndex) => {
const key = newChild.key || newIndex;
const old = oldMap.get(key);
if (!old) {
// 新增节点
patches.push({
type: 'INSERT',
node: newChild,
index: newIndex
});
} else if (old.child.type !== newChild.type) {
// 类型不同,替换
patches.push({
type: 'REPLACE',
oldNode: old.child,
newNode: newChild,
index: newIndex
});
} else {
// 类型相同,更新
patches.push({
type: 'UPDATE',
oldNode: old.child,
newNode: newChild,
index: newIndex
});
}
oldMap.delete(key);
});
// 剩余的旧节点需要删除
oldMap.forEach((old) => {
patches.push({
type: 'REMOVE',
node: old.child,
index: old.index
});
});
return patches;
}3.4 key的重要性
为什么需要key
jsx
// 没有key的问题
function TodoList() {
const [todos, setTodos] = useState([
{ id: 1, text: 'Task 1', done: false },
{ id: 2, text: 'Task 2', done: true }
]);
return (
<ul>
{todos.map(todo => (
<li>
<input type="checkbox" checked={todo.done} />
{todo.text}
</li>
))}
</ul>
);
}
// 问题:删除第一项时,第二项的checkbox状态会错误地保留正确使用key
jsx
// 使用key解决
function TodoList() {
const [todos, setTodos] = useState([
{ id: 1, text: 'Task 1', done: false },
{ id: 2, text: 'Task 2', done: true }
]);
return (
<ul>
{todos.map(todo => (
<li key={todo.id}> {/* 使用唯一的id作为key */}
<input type="checkbox" checked={todo.done} />
{todo.text}
</li>
))}
</ul>
);
}key的选择原则
javascript
// 好的key
// 1. 数据的唯一ID
<li key={item.id}>{item.text}</li>
// 2. 数据库主键
<div key={user.userId}>{user.name}</div>
// 3. 唯一的业务标识
<Article key={article.slug} {...article} />
// 不好的key
// 1. 数组索引(列表会重排序时)
<li key={index}>{item}</li> // 可能导致性能问题和bug
// 2. 随机数
<div key={Math.random()}>{content}</div> // 每次渲染都变,失去key的意义
// 3. 不稳定的值
<li key={item.timestamp}>{item.text}</li> // timestamp可能变化第四部分:Fiber架构与虚拟DOM
4.1 Fiber是什么
Fiber是React 16引入的新协调引擎,React 19继续使用并优化了Fiber架构。
传统虚拟DOM的问题
javascript
// React 15及之前:递归diff(同步、不可中断)
function updateComponent(component) {
// 1. 计算新的虚拟DOM
const newVDOM = component.render();
// 2. 递归diff
diff(oldVDOM, newVDOM);
// 3. 更新DOM
updateDOM(patches);
// 问题:如果组件树很大,这个过程会阻塞主线程
// 用户交互(点击、输入)会卡顿
}Fiber的解决方案
javascript
// React 16+:Fiber架构(可中断、可恢复)
function workLoop(deadline) {
let shouldYield = false;
while (nextUnitOfWork && !shouldYield) {
// 执行一小单元的工作
nextUnitOfWork = performUnitOfWork(nextUnitOfWork);
// 检查是否需要让出控制权
shouldYield = deadline.timeRemaining() < 1;
}
if (nextUnitOfWork) {
// 还有工作,继续调度
requestIdleCallback(workLoop);
} else {
// 工作完成,提交更新
commitRoot();
}
}
requestIdleCallback(workLoop);4.2 Fiber节点结构
javascript
// Fiber节点
const fiber = {
// 节点类型
type: 'div', // 元素类型或组件
key: null, // key
// 节点关系
child: null, // 第一个子节点
sibling: null, // 下一个兄弟节点
return: null, // 父节点
// 节点状态
stateNode: domElement, // 对应的真实DOM节点
props: { ... }, // 属性
memoizedState: null, // Hook状态链表
// 副作用
flags: 0, // 副作用标记(插入、更新、删除)
subtreeFlags: 0, // 子树副作用
// 调度
lanes: 0, // 优先级
// 双缓冲
alternate: null // 指向另一棵树的对应节点
};4.3 Fiber树的构建
双缓冲机制
javascript
// Current树:当前屏幕显示的内容
const currentFiber = {
type: 'div',
props: { className: 'old' },
stateNode: domElement,
child: null,
sibling: null,
return: null,
alternate: workInProgressFiber // 指向Work-in-Progress树
};
// Work-in-Progress树:正在构建的新树
const workInProgressFiber = {
type: 'div',
props: { className: 'new' },
stateNode: domElement, // 复用DOM节点
child: null,
sibling: null,
return: null,
alternate: currentFiber // 指向Current树
};
// 更新流程:
// 1. 基于Current树创建Work-in-Progress树
// 2. 在Work-in-Progress树上进行diff和标记副作用
// 3. 完成后,Work-in-Progress树变成新的Current树Fiber树遍历
javascript
// 深度优先遍历
function walkFiberTree(fiber) {
// 1. 处理当前节点
performUnitOfWork(fiber);
// 2. 如果有子节点,处理子节点
if (fiber.child) {
return fiber.child;
}
// 3. 如果没有子节点,找兄弟节点
let nextFiber = fiber;
while (nextFiber) {
if (nextFiber.sibling) {
return nextFiber.sibling;
}
// 4. 没有兄弟节点,回到父节点
nextFiber = nextFiber.return;
}
// 5. 遍历完成
return null;
}4.4 React 19的Fiber优化
自动批量更新
jsx
// React 18之前
function handleClick() {
setCount(c => c + 1); // 触发重新渲染
setName('Alice'); // 触发重新渲染
// 总共2次渲染
}
// React 18+(包括React 19)
function handleClick() {
setCount(c => c + 1);
setName('Alice');
// 自动批量处理,只触发1次渲染
}
// 异步也会批量处理(React 19增强)
async function handleClick() {
const data = await fetchData();
setData(data); // 批量处理
setLoading(false); // 批量处理
// 只触发1次渲染
}优先级调度
jsx
import { useTransition, useDeferredValue } from 'react';
function SearchResults() {
const [query, setQuery] = useState('');
const [isPending, startTransition] = useTransition();
function handleChange(e) {
// 高优先级更新(立即响应用户输入)
setQuery(e.target.value);
// 低优先级更新(搜索结果可以稍后更新)
startTransition(() => {
updateSearchResults(e.target.value);
});
}
return (
<>
<input value={query} onChange={handleChange} />
{isPending && <Spinner />}
<Results />
</>
);
}并发渲染
jsx
// React 19的并发特性
function App() {
return (
<Suspense fallback={<Loading />}>
<UserProfile /> {/* 可以并发渲染 */}
<Posts /> {/* 可以并发渲染 */}
</Suspense>
);
}
// Fiber会根据优先级调度这些组件的渲染
// 高优先级任务可以中断低优先级任务第五部分:性能优化原理
5.1 虚拟DOM的性能优势
批量更新
jsx
function Counter() {
const [count, setCount] = useState(0);
function handleClick() {
// 多次setState会被批量处理
setCount(c => c + 1);
setCount(c => c + 1);
setCount(c => c + 1);
// 只会触发一次虚拟DOM diff和一次真实DOM更新
}
return <button onClick={handleClick}>{count}</button>;
}跨平台渲染
javascript
// Web平台
import ReactDOM from 'react-dom/client';
ReactDOM.createRoot(root).render(<App />);
// Native平台
import { AppRegistry } from 'react-native';
AppRegistry.registerComponent('App', () => App);
// 同样的虚拟DOM,不同的渲染器
// 虚拟DOM抽象了平台差异5.2 React 19的编译优化
React Compiler
React 19引入了新的编译器,可以自动优化组件:
jsx
// 源代码
function TodoList({ todos, filter }) {
// 没有手动优化
const filteredTodos = todos.filter(todo => {
if (filter === 'active') return !todo.completed;
if (filter === 'completed') return todo.completed;
return true;
});
return (
<ul>
{filteredTodos.map(todo => (
<li key={todo.id}>{todo.text}</li>
))}
</ul>
);
}
// React 19编译器自动优化为:
function TodoList({ todos, filter }) {
// 自动添加useMemo
const filteredTodos = useMemo(() => {
return todos.filter(todo => {
if (filter === 'active') return !todo.completed;
if (filter === 'completed') return todo.completed;
return true;
});
}, [todos, filter]);
// 自动优化map渲染
return (
<ul>
{filteredTodos.map(todo => (
<li key={todo.id}>{todo.text}</li>
))}
</ul>
);
}静态标记
jsx
// 编译器识别静态内容
function Article({ title, content }) {
return (
<article>
{/* 静态部分:不会参与diff */}
<header className="article-header">
<div className="meta">
<span className="category">技术</span>
</div>
</header>
{/* 动态部分:需要diff */}
<h1>{title}</h1>
<div>{content}</div>
</article>
);
}
// 编译器优化:
// - 标记静态节点,跳过diff
// - 只diff动态节点
// - 减少虚拟DOM比较次数5.3 手动优化技巧
React.memo
jsx
// 避免不必要的重新渲染
const ExpensiveComponent = React.memo(function({ data }) {
// 复杂的渲染逻辑
return <div>{/* ... */}</div>;
});
// 只有data改变时才重新渲染
// 底层原理:浅比较props,决定是否重用虚拟DOMuseMemo和useCallback
jsx
function ParentComponent() {
const [count, setCount] = useState(0);
const [text, setText] = useState('');
// 缓存计算结果
const expensiveValue = useMemo(() => {
return computeExpensiveValue(count);
}, [count]);
// 缓存函数引用
const handleClick = useCallback(() => {
console.log(text);
}, [text]);
return (
<>
<button onClick={() => setCount(c => c + 1)}>{count}</button>
<input value={text} onChange={e => setText(e.target.value)} />
<ChildComponent value={expensiveValue} onClick={handleClick} />
</>
);
}
// 优化原理:
// - useMemo:避免重新计算,减少虚拟DOM创建
// - useCallback:避免创建新函数,减少子组件重新渲染key优化
jsx
// 正确使用key,提升列表diff效率
function MessageList({ messages }) {
return (
<div>
{messages.map(msg => (
<Message
key={msg.id} // 使用稳定的唯一ID
{...msg}
/>
))}
</div>
);
}
// Diff算法会利用key快速定位节点
// 避免不必要的DOM操作第六部分:实战案例
6.1 虚拟DOM调试
查看虚拟DOM结构
jsx
function DebugVirtualDOM() {
const element = (
<div className="container">
<h1>Title</h1>
<p>Content</p>
</div>
);
// 打印虚拟DOM对象
console.log(element);
// 输出:
// {
// $$typeof: Symbol(react.element),
// type: "div",
// props: {
// className: "container",
// children: [
// { type: "h1", props: { children: "Title" } },
// { type: "p", props: { children: "Content" } }
// ]
// }
// }
return element;
}监控Diff过程
jsx
// 使用React DevTools Profiler
import { Profiler } from 'react';
function App() {
function onRenderCallback(
id,
phase,
actualDuration,
baseDuration,
startTime,
commitTime
) {
console.log({
id, // Profiler id
phase, // "mount" 或 "update"
actualDuration, // 本次渲染耗时
baseDuration, // 不使用memo时的渲染耗时
startTime, // 开始渲染时间
commitTime // 提交更新时间
});
}
return (
<Profiler id="App" onRender={onRenderCallback}>
<MainContent />
</Profiler>
);
}6.2 性能对比实验
大列表渲染
jsx
// 未优化版本
function UnoptimizedList({ items }) {
return (
<ul>
{items.map((item, index) => (
<li key={index}> {/* 使用index作为key */}
<span>{item.name}</span>
<button onClick={() => handleDelete(item)}>删除</button>
</li>
))}
</ul>
);
}
// 优化版本
const OptimizedListItem = React.memo(({ item, onDelete }) => {
return (
<li>
<span>{item.name}</span>
<button onClick={onDelete}>删除</button>
</li>
);
});
function OptimizedList({ items }) {
const handleDelete = useCallback((id) => {
// 删除逻辑
}, []);
return (
<ul>
{items.map(item => (
<OptimizedListItem
key={item.id} {/* 使用唯一ID */}
item={item}
onDelete={() => handleDelete(item.id)}
/>
))}
</ul>
);
}
// 性能对比:
// 未优化:删除一项时,所有item重新渲染(因为index变了)
// 优化:删除一项时,只有一项被删除,其他项保持不变虚拟滚动
jsx
// 处理超大列表
import { FixedSizeList } from 'react-window';
function VirtualizedList({ items }) {
const Row = ({ index, style }) => (
<div style={style}>
{items[index].name}
</div>
);
return (
<FixedSizeList
height={600}
itemCount={items.length}
itemSize={50}
width="100%"
>
{Row}
</FixedSizeList>
);
}
// 优势:
// - 只渲染可见区域的DOM
// - 虚拟DOM tree大小固定
// - Diff计算量恒定
// - 支持百万级数据6.3 自定义虚拟DOM实现
简易版本
javascript
// 创建虚拟DOM
function createElement(type, props, ...children) {
return {
type,
props: {
...props,
children: children.flat().map(child =>
typeof child === 'object' ? child : createTextElement(child)
)
}
};
}
function createTextElement(text) {
return {
type: 'TEXT_ELEMENT',
props: {
nodeValue: text,
children: []
}
};
}
// 渲染到真实DOM
function render(vdom, container) {
const dom = vdom.type === 'TEXT_ELEMENT'
? document.createTextNode('')
: document.createElement(vdom.type);
// 设置属性
Object.keys(vdom.props)
.filter(key => key !== 'children')
.forEach(name => {
dom[name] = vdom.props[name];
});
// 递归渲染子节点
vdom.props.children.forEach(child =>
render(child, dom)
);
container.appendChild(dom);
}
// Diff算法
function diff(oldVDOM, newVDOM) {
// 类型不同,替换
if (oldVDOM.type !== newVDOM.type) {
return { type: 'REPLACE', newVDOM };
}
// 文本节点,比较内容
if (newVDOM.type === 'TEXT_ELEMENT') {
if (oldVDOM.props.nodeValue !== newVDOM.props.nodeValue) {
return { type: 'TEXT', content: newVDOM.props.nodeValue };
}
return null;
}
// 比较属性
const propPatches = diffProps(oldVDOM.props, newVDOM.props);
// 比较子节点
const childPatches = diffChildren(
oldVDOM.props.children,
newVDOM.props.children
);
if (propPatches || childPatches.length > 0) {
return { type: 'UPDATE', propPatches, childPatches };
}
return null;
}
// 应用补丁
function patch(dom, patches) {
if (!patches) return;
switch (patches.type) {
case 'REPLACE':
const newDom = render(patches.newVDOM);
dom.parentNode.replaceChild(newDom, dom);
break;
case 'TEXT':
dom.nodeValue = patches.content;
break;
case 'UPDATE':
// 更新属性
applyPropPatches(dom, patches.propPatches);
// 更新子节点
patches.childPatches.forEach((childPatch, i) => {
patch(dom.childNodes[i], childPatch);
});
break;
}
}
// 使用示例
const vdom1 = createElement('div', { className: 'container' },
createElement('h1', null, 'Hello'),
createElement('p', null, 'World')
);
const vdom2 = createElement('div', { className: 'container' },
createElement('h1', null, 'Hi'),
createElement('p', null, 'React')
);
const container = document.getElementById('root');
render(vdom1, container);
const patches = diff(vdom1, vdom2);
patch(container.firstChild, patches);6.4 React 19 Server Components与虚拟DOM
jsx
// Server Component(在服务器上运行)
async function ProductList() {
// 在服务器上获取数据
const products = await db.products.findAll();
// 返回虚拟DOM
return (
<div className="products">
{products.map(product => (
<ProductCard key={product.id} product={product} />
))}
</div>
);
}
// Client Component(在浏览器运行)
'use client';
function ProductCard({ product }) {
const [quantity, setQuantity] = useState(1);
// 客户端交互
return (
<div className="card">
<h3>{product.name}</h3>
<p>${product.price}</p>
<input
type="number"
value={quantity}
onChange={e => setQuantity(Number(e.target.value))}
/>
<button>添加到购物车</button>
</div>
);
}
// 渲染流程:
// 1. Server Component在服务器生成虚拟DOM
// 2. 序列化为JSON发送到客户端
// 3. 客户端重建虚拟DOM树
// 4. Client Component在客户端生成交互式虚拟DOM
// 5. 合并两部分虚拟DOM
// 6. 渲染到真实DOM第七部分:深入理解
7.1 虚拟DOM的局限性
1. 内存占用
javascript
// 大型应用中,虚拟DOM也会占用大量内存
const largeList = Array(10000).fill(0).map((_, i) => ({
id: i,
name: `Item ${i}`,
description: `Description for item ${i}`
}));
// 每个虚拟DOM节点都是对象,占用内存
// 10000个项 × 每项多个节点 = 大量内存占用
// 解决方案:虚拟滚动、懒加载2. 初次渲染不一定更快
javascript
// 首次渲染时,虚拟DOM也需要创建
// 没有diff优势
// 直接操作DOM(首次可能更快)
const div = document.createElement('div');
div.textContent = 'Hello';
document.body.appendChild(div);
// 虚拟DOM方式(需要额外的虚拟DOM创建步骤)
const vdom = { type: 'div', props: { children: 'Hello' } };
const realDOM = render(vdom);
document.body.appendChild(realDOM);
// 但虚拟DOM的优势在于后续更新3. 过度渲染问题
jsx
// 父组件状态变化,所有子组件都会重新创建虚拟DOM
function Parent() {
const [count, setCount] = useState(0);
return (
<div>
<button onClick={() => setCount(c => c + 1)}>{count}</button>
<ExpensiveChild1 /> {/* 即使props没变,也会重新渲染 */}
<ExpensiveChild2 /> {/* 创建新的虚拟DOM */}
<ExpensiveChild3 /> {/* 进行diff比较 */}
</div>
);
}
// 需要手动优化(React.memo、useMemo等)7.2 与其他框架对比
Vue 3的编译时优化
vue
<!-- Vue 3模板 -->
<template>
<div class="container">
<h1>{{ title }}</h1>
<p>Static content</p>
<p>{{ description }}</p>
</div>
</template>
<!-- Vue编译器会:-->
<!-- 1. 标记静态节点(<p>Static content</p>)-->
<!-- 2. 提取静态节点,避免重新创建 -->
<!-- 3. 只diff动态内容 -->
<!-- 4. 性能优于React的纯虚拟DOM -->Svelte的无虚拟DOM
svelte
<!-- Svelte编译为纯JavaScript -->
<script>
let count = 0;
</script>
<button on:click={() => count++}>
{count}
</button>
<!-- 编译结果:直接操作DOM,无虚拟DOM -->
<!--
function update(value) {
count = value;
button.textContent = count; // 直接更新DOM
}
-->React 19的改进
jsx
// React 19编译器:结合两者优势
// 1. 保留虚拟DOM(跨平台、时间切片)
// 2. 编译时优化(静态标记、自动memo)
// 3. 运行时优化(Fiber、并发渲染)
function App() {
const [count, setCount] = useState(0);
return (
<div>
{/* 编译器自动识别静态部分 */}
<header className="static-header">
<Logo />
</header>
{/* 动态部分 */}
<main>
<Counter value={count} onChange={setCount} />
</main>
</div>
);
}
// 编译器优化:
// - 标记静态节点,跳过diff
// - 自动添加memo
// - 优化事件处理器7.3 未来展望
1. 更智能的编译器
jsx
// React未来的编译器可能会:
// - 自动检测组件纯度
// - 智能提取常量
// - 优化闭包引用
// - 消除不必要的重新渲染
// 源代码
function TodoList({ todos }) {
return (
<ul>
{todos.map(todo => (
<li key={todo.id}>
<span className="text">{todo.text}</span>
<button className="delete-btn">删除</button>
</li>
))}
</ul>
);
}
// 编译器可能优化为:
// - 提取className常量
// - 优化map回调
// - 减少闭包创建2. 更细粒度的响应式
jsx
// 类似Vue 3的响应式系统
// React可能引入更细粒度的依赖追踪
function Component() {
const user = useReactive({
name: 'Alice',
age: 25,
address: {
city: 'Beijing'
}
});
return (
<div>
{/* 只有name变化时,这部分才重新渲染 */}
<p>{user.name}</p>
{/* 只有age变化时,这部分才重新渲染 */}
<p>{user.age}</p>
</div>
);
}3. 原生支持
jsx
// React可能利用浏览器原生特性
// 例如:Template Element、Custom Elements
// 使用<template>缓存静态内容
const staticTemplate = document.createElement('template');
staticTemplate.innerHTML = '<div class="static">Static Content</div>';
// 克隆而非重新创建
const clone = staticTemplate.content.cloneNode(true);第八部分:总结与实践
8.1 核心要点
虚拟DOM是什么
- JavaScript对象表示的DOM树
- 真实DOM的轻量级副本
- React性能优化的核心机制
JSX到虚拟DOM的转换
- React 18:
React.createElement() - React 19:
jsx()/jsxs()(更高效) - 编译时优化,减少运行时开销
- React 18:
Diff算法
- O(n)复杂度
- 同层比较
- key优化列表diff
Fiber架构
- 可中断的协调过程
- 优先级调度
- 并发渲染
性能优化
- React.memo避免重复渲染
- useMemo缓存计算结果
- useCallback缓存函数引用
- 正确使用key
- React 19编译器自动优化
8.2 最佳实践
合理使用key
- 使用稳定、唯一的ID
- 避免使用index(列表会重排序时)
避免内联对象和函数
jsx// 不好 <Child style={{ margin: 10 }} onClick={() => {}} /> // 好 const style = { margin: 10 }; const handleClick = useCallback(() => {}, []); <Child style={style} onClick={handleClick} />使用React DevTools分析
- Profiler分析渲染性能
- Components树查看虚拟DOM
- 识别性能瓶颈
拥抱React 19新特性
- 使用新的JSX转换
- 启用React Compiler
- 利用自动批量更新
8.3 练习项目
实现简易虚拟DOM库
- createElement函数
- render函数
- diff算法
- patch函数
性能优化实战
- 大列表优化
- 虚拟滚动实现
- 性能监控
Fiber模拟器
- 理解时间切片
- 实现简单的调度器
- 优先级队列
8.4 学习资源
- React源码:https://github.com/facebook/react
- React文档:https://react.dev
- Fiber架构介绍:https://github.com/acdlite/react-fiber-architecture
- 虚拟DOM原理:深入理解React虚拟DOM
通过本章学习,你已经深入理解了JSX与虚拟DOM的关系,掌握了React底层的工作原理。这些知识将帮助你编写更高效的React应用,理解性能优化的本质,成为更优秀的React开发者。
继续深入学习,探索React的更多奥秘!