Skip to content

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算法基于三个假设:

  1. 不同类型的元素会产生不同的树
  2. 开发者可以通过key来标识哪些子元素在不同渲染中保持稳定
  3. 只对同层级元素进行比较,不跨层级比较

传统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. 递归处理组件返回的虚拟DOM

3.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,决定是否重用虚拟DOM

useMemo和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 核心要点

  1. 虚拟DOM是什么

    • JavaScript对象表示的DOM树
    • 真实DOM的轻量级副本
    • React性能优化的核心机制
  2. JSX到虚拟DOM的转换

    • React 18:React.createElement()
    • React 19:jsx() / jsxs()(更高效)
    • 编译时优化,减少运行时开销
  3. Diff算法

    • O(n)复杂度
    • 同层比较
    • key优化列表diff
  4. Fiber架构

    • 可中断的协调过程
    • 优先级调度
    • 并发渲染
  5. 性能优化

    • React.memo避免重复渲染
    • useMemo缓存计算结果
    • useCallback缓存函数引用
    • 正确使用key
    • React 19编译器自动优化

8.2 最佳实践

  1. 合理使用key

    • 使用稳定、唯一的ID
    • 避免使用index(列表会重排序时)
  2. 避免内联对象和函数

    jsx
    // 不好
    <Child style={{ margin: 10 }} onClick={() => {}} />
    
    // 好
    const style = { margin: 10 };
    const handleClick = useCallback(() => {}, []);
    <Child style={style} onClick={handleClick} />
  3. 使用React DevTools分析

    • Profiler分析渲染性能
    • Components树查看虚拟DOM
    • 识别性能瓶颈
  4. 拥抱React 19新特性

    • 使用新的JSX转换
    • 启用React Compiler
    • 利用自动批量更新

8.3 练习项目

  1. 实现简易虚拟DOM库

    • createElement函数
    • render函数
    • diff算法
    • patch函数
  2. 性能优化实战

    • 大列表优化
    • 虚拟滚动实现
    • 性能监控
  3. Fiber模拟器

    • 理解时间切片
    • 实现简单的调度器
    • 优先级队列

8.4 学习资源

通过本章学习,你已经深入理解了JSX与虚拟DOM的关系,掌握了React底层的工作原理。这些知识将帮助你编写更高效的React应用,理解性能优化的本质,成为更优秀的React开发者。

继续深入学习,探索React的更多奥秘!