Skip to content

深入理解 React 渲染管线

从 JSX → createElement → Fiber → DOM 的完整链路、beginWork/completeWork 各 tag 处理逻辑

什么是渲染管线?

定义:渲染管线(Render Pipeline)是 React 将开发者编写的 JSX 代码最终转换为屏幕上像素的完整流水线。理解这条管线,就理解了 React 的核心工作流程。


一、JSX → React Element

JSX 编译

jsx
// 你写的 JSX
<div className="card">
  <h1>标题</h1>
  <Button onClick={handleClick}>点击</Button>
</div>

// Babel / SWC 编译后(React 17+ 的新 JSX Transform)
import { jsx as _jsx, jsxs as _jsxs } from 'react/jsx-runtime';

_jsxs("div", {
  className: "card",
  children: [
    _jsx("h1", { children: "标题" }),
    _jsx(Button, { onClick: handleClick, children: "点击" })
  ]
});

// 生成的 React Element(普通 JS 对象)
{
  $$typeof: Symbol.for('react.element'), // 安全标记,防止 XSS
  type: "div",                            // 字符串=DOM元素,函数=组件
  key: null,
  ref: null,
  props: {
    className: "card",
    children: [
      { $$typeof: Symbol.for('react.element'), type: "h1", props: { children: "标题" } },
      { $$typeof: Symbol.for('react.element'), type: Button, props: { onClick: fn, children: "点击" } }
    ]
  }
}

$$typeof 的安全作用

javascript
// 为什么需要 Symbol.for('react.element')?
// 防止 XSS 攻击:如果服务端返回的 JSON 被注入恶意 React Element
// JSON 无法包含 Symbol → 攻击者无法伪造有效的 React Element

// 服务端返回的恶意数据
const malicious = {
  type: "script",
  props: { dangerouslySetInnerHTML: { __html: "alert('XSS')" } }
};
// 缺少 $$typeof: Symbol.for('react.element')
// React 不会将其识别为合法元素 → 不渲染

二、React Element → Fiber

首次渲染(Mount)

createRoot(container).render(<App />)

创建 FiberRoot 和 HostRoot Fiber

从 App Element 创建 App Fiber

进入 Render 阶段(beginWork + completeWork)

Fiber 创建

javascript
function createFiberFromElement(element) {
  const { type, key, props } = element;

  let tag; // Fiber 的类型标记
  if (typeof type === 'function') {
    if (type.prototype?.isReactComponent) {
      tag = ClassComponent;      // 类组件
    } else {
      tag = FunctionComponent;   // 函数组件
    }
  } else if (typeof type === 'string') {
    tag = HostComponent;         // DOM 元素(div, span...)
  } else if (type === REACT_FRAGMENT_TYPE) {
    tag = Fragment;
  } else if (type === REACT_SUSPENSE_TYPE) {
    tag = SuspenseComponent;
  }
  // ...其他类型

  const fiber = {
    tag,
    type,
    key,
    pendingProps: props,
    stateNode: null,    // DOM 节点或类实例(后面创建)
    return: null,       // 父 Fiber(后面设置)
    child: null,
    sibling: null,
    alternate: null,
    flags: NoFlags,
    memoizedState: null, // Hooks 链表头
    updateQueue: null,
  };

  return fiber;
}

三、Render 阶段 —— beginWork

beginWork 是的过程:从根节点向下,为每个 Fiber 创建/更新子 Fiber。

总体流程

javascript
function beginWork(current, workInProgress, renderLanes) {
  // current: 当前屏幕上的 Fiber(首次渲染为 null)
  // workInProgress: 正在构建的 Fiber

  // === Bailout 优化 ===
  if (current !== null) {
    const oldProps = current.memoizedProps;
    const newProps = workInProgress.pendingProps;
    if (oldProps === newProps && !hasContextChanged() &&
        (current.lanes & renderLanes) === 0) {
      // Props 没变、Context 没变、没有待处理更新 → 跳过
      return bailoutOnAlreadyFinishedWork(current, workInProgress);
    }
  }

  // === 根据 tag 分发 ===
  switch (workInProgress.tag) {
    case FunctionComponent:
      return updateFunctionComponent(current, workInProgress, renderLanes);
    case ClassComponent:
      return updateClassComponent(current, workInProgress, renderLanes);
    case HostComponent:
      return updateHostComponent(current, workInProgress, renderLanes);
    case HostText:
      return updateHostText(current, workInProgress);
    case SuspenseComponent:
      return updateSuspenseComponent(current, workInProgress, renderLanes);
    case Fragment:
      return updateFragment(current, workInProgress, renderLanes);
    case ContextProvider:
      return updateContextProvider(current, workInProgress, renderLanes);
    case ContextConsumer:
      return updateContextConsumer(current, workInProgress, renderLanes);
    case MemoComponent:
      return updateMemoComponent(current, workInProgress, renderLanes);
    case LazyComponent:
      return mountLazyComponent(current, workInProgress, renderLanes);
    // ...更多类型
  }
}

FunctionComponent 处理

javascript
function updateFunctionComponent(current, workInProgress, renderLanes) {
  const Component = workInProgress.type;     // 函数本身
  const props = workInProgress.pendingProps;

  // 执行函数组件(调用 Hooks)
  const children = renderWithHooks(
    current,
    workInProgress,
    Component,
    props,
    renderLanes
  );

  // 如果是更新且没有状态变化,尝试 bailout
  if (current !== null && !didReceiveUpdate) {
    bailoutHooks(current, workInProgress);
    return bailoutOnAlreadyFinishedWork(current, workInProgress);
  }

  // Diff 子节点,创建子 Fiber
  reconcileChildren(current, workInProgress, children, renderLanes);
  return workInProgress.child; // 返回第一个子 Fiber,继续向下
}

HostComponent(DOM 元素)处理

javascript
function updateHostComponent(current, workInProgress, renderLanes) {
  // type = "div", "span", "input" 等
  const type = workInProgress.type;
  const newProps = workInProgress.pendingProps;
  const oldProps = current?.memoizedProps;

  // children 来自 props.children
  let nextChildren = newProps.children;

  // 优化:纯文本子节点不创建 Fiber
  const isDirectTextChild = typeof nextChildren === 'string' ||
                             typeof nextChildren === 'number';
  if (isDirectTextChild) {
    nextChildren = null; // 文本节点在 completeWork 中处理
  }

  // Diff 子节点
  reconcileChildren(current, workInProgress, nextChildren, renderLanes);
  return workInProgress.child;
}

SuspenseComponent 处理

javascript
function updateSuspenseComponent(current, workInProgress, renderLanes) {
  const nextProps = workInProgress.pendingProps;

  let showFallback = false;
  const didSuspend = (workInProgress.flags & DidCapture) !== NoFlags;

  if (didSuspend) {
    // 子组件 throw 了 Promise → 显示 fallback
    showFallback = true;
    workInProgress.flags &= ~DidCapture;
  }

  if (showFallback) {
    // 渲染 fallback 内容
    const fallbackChildren = nextProps.fallback;
    return mountSuspenseFallbackChildren(workInProgress, fallbackChildren);
  } else {
    // 正常渲染子节点
    const primaryChildren = nextProps.children;
    return mountSuspensePrimaryChildren(workInProgress, primaryChildren);
  }
}

四、Render 阶段 —— completeWork

completeWork 是的过程:从叶子节点向上,创建/更新 DOM 节点,收集副作用。

javascript
function completeWork(current, workInProgress, renderLanes) {
  const newProps = workInProgress.pendingProps;

  switch (workInProgress.tag) {
    case HostComponent: {
      const type = workInProgress.type;

      if (current !== null && workInProgress.stateNode !== null) {
        // === 更新 ===
        // 比较新旧 props,收集需要更新的属性
        const updatePayload = diffProperties(
          workInProgress.stateNode,
          type,
          current.memoizedProps,
          newProps
        );
        // updatePayload = ['className', 'new-class', 'style', { color: 'red' }]
        if (updatePayload) {
          workInProgress.flags |= Update; // 标记需要更新
          workInProgress.updateQueue = updatePayload;
        }
      } else {
        // === 首次渲染 ===
        // 创建 DOM 节点
        const instance = document.createElement(type);

        // 设置属性
        setInitialProperties(instance, type, newProps);

        // 将所有子 DOM 节点插入
        appendAllChildren(instance, workInProgress);

        // 绑定 Fiber ↔ DOM
        workInProgress.stateNode = instance;
      }

      // 冒泡收集子树的副作用标记
      bubbleProperties(workInProgress);
      return null;
    }

    case HostText: {
      const newText = newProps;
      if (current !== null && workInProgress.stateNode !== null) {
        // 更新文本
        if (current.memoizedProps !== newText) {
          workInProgress.flags |= Update;
        }
      } else {
        // 创建文本节点
        workInProgress.stateNode = document.createTextNode(newText);
      }
      bubbleProperties(workInProgress);
      return null;
    }

    case FunctionComponent:
    case Fragment:
      // 函数组件和 Fragment 没有对应 DOM
      // 只需要冒泡收集副作用
      bubbleProperties(workInProgress);
      return null;
  }
}

副作用冒泡

javascript
function bubbleProperties(completedWork) {
  let subtreeFlags = NoFlags;
  let child = completedWork.child;

  while (child !== null) {
    // 收集子节点和子树的所有标记
    subtreeFlags |= child.subtreeFlags;
    subtreeFlags |= child.flags;
    child = child.sibling;
  }

  completedWork.subtreeFlags = subtreeFlags;
}

// 作用:Commit 阶段可以通过 subtreeFlags 快速判断子树是否有副作用
// 如果 subtreeFlags === NoFlags → 跳过整棵子树,极大优化 Commit 性能

五、Commit 阶段

三个子阶段

javascript
function commitRoot(root) {
  const finishedWork = root.finishedWork;

  // 1. BeforeMutation 阶段
  commitBeforeMutationEffects(finishedWork);
  //   - getSnapshotBeforeUpdate(类组件)
  //   - 调度 useEffect(异步)

  // 2. Mutation 阶段(操作 DOM)
  commitMutationEffects(finishedWork);
  //   - Placement: insertBefore / appendChild
  //   - Update: 更新 DOM 属性
  //   - Deletion: removeChild + 清理
  //   - useInsertionEffect 回调

  // === 切换 Current 树 ===
  root.current = finishedWork;

  // 3. Layout 阶段
  commitLayoutEffects(finishedWork);
  //   - useLayoutEffect 回调(同步)
  //   - componentDidMount / componentDidUpdate
  //   - 更新 ref

  // 4. 异步调度 useEffect
  scheduleCallback(NormalPriority, () => {
    flushPassiveEffects(); // 执行 useEffect 的清理和回调
  });
}

Mutation 阶段的 DOM 操作

javascript
function commitMutationEffects(fiber) {
  // 递归遍历 Fiber 树
  if (fiber.subtreeFlags & MutationMask) {
    // 子树有副作用,继续遍历子节点
    commitMutationEffects(fiber.child);
  }

  if (fiber.flags & Placement) {
    // 新增:找到父 DOM 节点,插入
    const parentDOM = getHostParent(fiber);
    const before = getHostSibling(fiber); // 找到下一个兄弟 DOM
    if (before) {
      parentDOM.insertBefore(fiber.stateNode, before);
    } else {
      parentDOM.appendChild(fiber.stateNode);
    }
  }

  if (fiber.flags & Update) {
    // 更新:应用 diffProperties 的结果
    const updatePayload = fiber.updateQueue;
    updateDOMProperties(fiber.stateNode, updatePayload);
    // ['className', 'new-class'] → element.className = 'new-class'
  }

  if (fiber.flags & Deletion) {
    // 删除:递归卸载子树
    commitDeletion(fiber);
    // removeChild + 执行 useEffect 清理 + 解绑 ref
  }
}

六、完整渲染流程图

JSX 代码
  ↓ Babel/SWC 编译
React Element(普通 JS 对象)
  ↓ createFiberFromElement
Fiber 节点
  ↓ 构建 WorkInProgress 树

  ├── beginWork(递)
  │   ├── FunctionComponent → renderWithHooks → reconcileChildren
  │   ├── HostComponent → reconcileChildren
  │   └── ...其他类型
  │   └── 返回 child Fiber

  └── completeWork(归)
      ├── HostComponent → createElement + setProperties + appendChildren
      ├── 收集副作用标记(flags + subtreeFlags)
      └── 返回 sibling 或 return

Commit 阶段(不可中断)
  ├── BeforeMutation → getSnapshotBeforeUpdate
  ├── Mutation → DOM 操作(插入/更新/删除)
  ├── current 切换
  ├── Layout → useLayoutEffect + ref 更新
  └── 异步 → useEffect

屏幕更新

面试高频题

Q: JSX 是什么?它是怎么变成 DOM 的?

JSX 是语法糖,由 Babel/SWC 编译为 jsx() 函数调用,返回 React Element(普通 JS 对象,描述 UI 结构)。React 根据 Element 创建 Fiber 节点,通过 Render 阶段(beginWork/completeWork)构建 Fiber 树并创建 DOM 节点,最后在 Commit 阶段将 DOM 操作应用到真实 DOM。

Q: beginWork 和 completeWork 分别做什么?

  • beginWork(递):从根节点向下,根据组件类型执行不同逻辑(调用函数组件、Diff 子节点),返回子 Fiber
  • completeWork(归):从叶子节点向上,创建/更新 DOM 节点,计算属性 diff,收集副作用标记(subtreeFlags 冒泡)

Q: React 的 $$typeof 是什么?为什么用 Symbol?

$$typeof: Symbol.for('react.element') 是 React Element 的类型标记。使用 Symbol 是为了防止 XSS 攻击:如果攻击者通过 JSON 注入伪造的 React Element,由于 JSON 无法包含 Symbol 值,React 不会将其识别为合法元素,从而拒绝渲染。

Q: Commit 阶段的三个子阶段?

  1. BeforeMutation:DOM 变更前,执行 getSnapshotBeforeUpdate、调度 useEffect
  2. Mutation:执行实际的 DOM 操作(insertBefore/appendChild/removeChild/属性更新),然后切换 current 树
  3. Layout:DOM 已更新,执行 useLayoutEffect、更新 ref、执行 componentDidMount/Update