返回首页

说说对 React 的 Fiber 架构的理解?

问题解析(面试官考察点)

面试官通过此问题主要考察:

  • 对 React Fiber 架构设计目标的理解
  • 对 Fiber 数据结构的了解
  • 对 React 渲染阶段和提交阶段分离的理解
  • 对时间切片和优先级调度的认识
  • 对 Fiber 如何解决递归渲染卡顿问题的理解

核心概念(基础知识点)

什么是 Fiber

Fiber 是 React 16 引入的新的协调引擎(Reconciliation Engine),主要目标是:

  1. 可中断的渲染: 将渲染工作拆分成小单元,可暂停和恢复
  2. 优先级调度: 根据任务优先级决定执行顺序
  3. 更好的错误处理: 支持错误边界(Error Boundaries)
  4. 为并发特性铺路: 支持 Suspense、Concurrent Mode 等特性

为什么需要 Fiber

在 React 15 及之前,协调过程是递归执行的,存在以下问题:

  1. 阻塞主线程: 大量组件更新时,浏览器无法响应用户输入
  2. 无法中断: 一旦开始渲染,必须等到完成才能停止
  3. 优先级单一: 所有更新一视同仁,无法优先处理重要更新

详细解答(代码示例)

Fiber 数据结构

// Fiber 节点的基本结构
function FiberNode(
  tag: WorkTag,
  pendingProps: mixed,
  key: null | string,
  mode: TypeOfMode
) {
  // 实例相关
  this.tag = tag;                    // Fiber 类型(函数组件、类组件、HostComponent 等)
  this.key = key;                    // key 属性
  this.elementType = null;           // 元素类型
  this.type = null;                  // 组件类型
  this.stateNode = null;             // 对应的真实 DOM 或组件实例

  // Fiber 树结构
  this.return = null;                // 父 Fiber
  this.child = null;                 // 第一个子 Fiber
  this.sibling = null;               // 下一个兄弟 Fiber
  this.index = 0;                    // 在兄弟节点中的索引

  // 工作单元相关
  this.pendingProps = pendingProps;  // 新的 props
  this.memoizedProps = null;         // 上一次渲染的 props
  this.memoizedState = null;         // 上一次渲染的 state
  this.updateQueue = null;           // 更新队列

  // 副作用相关
  this.flags = NoFlags;              // 副作用标记(Placement、Update、Deletion 等)
  this.subtreeFlags = NoFlags;       // 子树的副作用标记
  this.deletions = null;             // 要删除的子节点

  // 调度相关
  this.lanes = NoLanes;              // 当前 Fiber 的更新优先级
  this.childLanes = NoLanes;         // 子树的更新优先级

  // 双缓冲
  this.alternate = null;             // 指向对应的另一棵树上的 Fiber
}

Fiber 树结构

┌─────────────────────────────────────────────────────────────┐
│                    Fiber 树结构示意                          │
├─────────────────────────────────────────────────────────────┤
│                                                              │
│                         Root Fiber                          │
│                              │                              │
│              ┌───────────────┼───────────────┐              │
│              ▼               ▼               ▼              │
│          Fiber A         Fiber B         Fiber C            │
│              │               │                              │
│      ┌───────┴───────┐       │                              │
│      ▼               ▼       ▼                              │
│  Fiber A1        Fiber A2  Fiber B1                         │
│                                                              │
│  链表结构:                                                   │
│  child: 指向第一个子节点                                     │
│  sibling: 指向下一个兄弟节点                                 │
│  return: 指向父节点                                          │
│                                                              │
│  遍历顺序(深度优先):                                       │
│  Root → A → A1 → A2 → B → B1 → C                           │
│                                                              │
└─────────────────────────────────────────────────────────────┘

双缓冲技术

// React 使用双缓冲技术维护两棵 Fiber 树

// current 树:当前显示在屏幕上的 UI
const currentFiber = {
  type: 'div',
  stateNode: domNode,      // 对应的真实 DOM
  memoizedProps: { id: 'app' },
  memoizedState: null,
  alternate: workInProgressFiber  // 指向 workInProgress
};

// workInProgress 树:正在构建的 UI
const workInProgressFiber = {
  type: 'div',
  stateNode: null,         // 还未创建或更新
  pendingProps: { id: 'app', className: 'new' },
  memoizedState: null,
  alternate: currentFiber   // 指向 current
};

// 渲染完成后,交换两棵树
// currentFiber = workInProgressFiber

工作循环(Work Loop)

// React 的工作循环实现(简化版)

// 是否应该让出主线程
const shouldYield = () => {
  return deadline.timeRemaining() < 1;  // 没有时间了
};

// 工作循环
function workLoopConcurrent() {
  // 还有工作且不需要让出时,继续执行
  while (workInProgress !== null && !shouldYield()) {
    workInProgress = performUnitOfWork(workInProgress);
  }
}

// 执行一个工作单元
function performUnitOfWork(unitOfWork) {
  // 处理当前 Fiber,创建或更新子 Fiber
  const next = beginWork(unitOfWork);

  if (next === null) {
    // 没有子节点了,完成当前分支
    completeUnitOfWork(unitOfWork);
  }

  return next;
}

// 完成工作单元
function completeUnitOfWork(unitOfWork) {
  let completedWork = unitOfWork;

  do {
    // 完成当前 Fiber 的工作
    completeWork(completedWork);

    const siblingFiber = completedWork.sibling;
    if (siblingFiber !== null) {
      // 有兄弟节点,继续处理兄弟
      return siblingFiber;
    }

    // 回到父节点
    completedWork = completedWork.return;
  } while (completedWork !== null);
}

时间切片(Time Slicing)

// 时间切片演示
import { useState, useTransition } from "react";

function TimeSlicingDemo() {
  const [inputValue, setInputValue] = useState("");
  const [list, setList] = useState([]);
  const [isPending, startTransition] = useTransition();

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

    // 低优先级更新(可中断)
    startTransition(() => {
      const newList = Array(10000)
        .fill(null)
        .map((_, i) => `${value} - ${i}`);
      setList(newList);
    });
  };

  return (
    <div>
      <input
        value={inputValue}
        onChange={handleChange}
        placeholder="输入内容..."
      />
      {isPending && <span>加载中...</span>}
      <ul>
        {list.map((item, index) => (
          <li key={index}>{item}</li>
        ))}
      </ul>
    </div>
  );
}

优先级调度

// React 的 Lane 优先级模型(简化)

// 优先级定义(数值越小优先级越高)
export const SyncLane = /*                        */ 0b0000000000000000000000000000001;
export const InputContinuousHydrationLane = /*    */ 0b0000000000000000000000000000010;
export const InputContinuousLane = /*             */ 0b0000000000000000000000000000100;
export const DefaultHydrationLane = /*            */ 0b0000000000000000000000000001000;
export const DefaultLane = /*                     */ 0b0000000000000000000000000010000;
export const TransitionHydrationLane = /*         */ 0b0000000000000000000000000100000;
export const TransitionLane1 = /*                 */ 0b0000000000000000000000001000000;
// ... 更多 Transition Lane
export const IdleLane = /*                        */ 0b0100000000000000000000000000000;

// 获取最高优先级的 Lane
function getHighestPriorityLane(lanes) {
  return lanes & -lanes;
}

// 判断是否应该让出主线程
function shouldYieldToHost() {
  const timeElapsed = getCurrentTime() - startTime;
  if (timeElapsed < yieldInterval) {
    return false;
  }
  return true;
}

深入理解(原理剖析)

渲染的两个阶段

┌─────────────────────────────────────────────────────────────┐
│                   React 渲染的两个阶段                       │
├─────────────────────────────────────────────────────────────┤
│                                                              │
│  ┌─────────────────────────────────────────────────────┐   │
│  │              Render Phase(渲染阶段)                │   │
│  │                                                      │   │
│  │  - 构建 Fiber 树                                     │   │
│  │  - 执行协调(Reconciliation)                        │   │
│  │  - 计算需要做的更改                                  │   │
│  │  - 标记副作用                                        │   │
│  │  - 可中断、可恢复                                    │   │
│  │                                                      │   │
│  │  生命周期:                                          │   │
│  │  - constructor                                       │   │
│  │  - static getDerivedStateFromProps                   │   │
│  │  - render                                            │   │
│  │  - 函数组件体                                        │   │
│  │                                                      │   │
│  └─────────────────────────────────────────────────────┘   │
│                          │                                   │
│                          ▼                                   │
│  ┌─────────────────────────────────────────────────────┐   │
│  │              Commit Phase(提交阶段)                │   │
│  │                                                      │   │
│  │  - 执行 DOM 操作                                     │   │
│  │  - 执行副作用                                        │   │
│  │  - 不可中断                                          │   │
│  │                                                      │   │
│  │  生命周期:                                          │   │
│  │  - componentDidMount                                 │   │
│  │  - componentDidUpdate                                │   │
│  │  - componentWillUnmount                              │   │
│  │  - useEffect / useLayoutEffect 回调                  │   │
│  │                                                      │   │
│  └─────────────────────────────────────────────────────┘   │
│                                                              │
└─────────────────────────────────────────────────────────────┘

Fiber 的遍历算法

// Fiber 树的遍历(深度优先)

function walkFiber(fiber) {
  let current = fiber;

  while (current !== null) {
    // 处理当前节点
    console.log('访问节点:', current.type);

    // 如果有子节点,先处理子节点
    if (current.child !== null) {
      current = current.child;
      continue;
    }

    // 如果没有子节点,处理兄弟节点
    if (current.sibling !== null) {
      current = current.sibling;
      continue;
    }

    // 如果没有兄弟节点,回到父节点,再找父节点的兄弟
    while (current !== null) {
      current = current.return;
      if (current !== null && current.sibling !== null) {
        current = current.sibling;
        break;
      }
    }
  }
}

// 遍历顺序示例:
//     A
//    / \
//   B   E
//  / \
// C   D
//
// 遍历顺序:A → B → C → D → E

副作用的收集和执行

// 副作用标记
const Placement = /*             */ 0b00000000000000000000000010;  // 插入
const Update = /*                */ 0b00000000000000000000000100;  // 更新
const Deletion = /*              */ 0b00000000000000000000001000;  // 删除
const ChildDeletion = /*         */ 0b00000000000000000000010000;  // 删除子节点
const Ref = /*                   */ 0b00000000000000000000100000;  // ref 更新

// 收集副作用
function commitWork(finishedWork) {
  switch (finishedWork.flags) {
    case Placement:
      // 插入 DOM
      insertOrAppendPlacementNode(finishedWork);
      break;
    case Update:
      // 更新 DOM
      commitUpdate(finishedWork);
      break;
    case Deletion:
      // 删除 DOM
      commitDeletion(finishedWork);
      break;
  }
}

// 提交阶段执行副作用
function commitRoot(root) {
  const finishedWork = root.finishedWork;

  // 1. Before Mutation 阶段
  commitBeforeMutationEffects(finishedWork);

  // 2. Mutation 阶段(执行 DOM 操作)
  commitMutationEffects(finishedWork);

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

  // 4. Layout 阶段(执行 useLayoutEffect 等)
  commitLayoutEffects(finishedWork);
}

最佳实践

1. 使用 React 18 的并发特性

// 使用 useTransition 实现非阻塞更新
import { useTransition, useState } from "react";

function SearchResults() {
  const [query, setQuery] = useState("");
  const [results, setResults] = useState([]);
  const [isPending, startTransition] = useTransition();

  const handleSearch = (value) => {
    // 立即更新输入框
    setQuery(value);

    // 延迟更新搜索结果
    startTransition(() => {
      const searchResults = performExpensiveSearch(value);
      setResults(searchResults);
    });
  };

  return (
    <div>
      <input
        value={query}
        onChange={(e) => handleSearch(e.target.value)}
      />
      {isPending && <Spinner />}
      <Results data={results} />
    </div>
  );
}

// 使用 useDeferredValue 延迟更新
import { useDeferredValue } from "react";

function Chart({ data }) {
  // 使用延迟值,允许优先渲染其他更新
  const deferredData = useDeferredValue(data);

  const isStale = data !== deferredData;

  return (
    <div style={{ opacity: isStale ? 0.5 : 1 }}>
      <ExpensiveChart data={deferredData} />
    </div>
  );
}

2. 合理使用 Suspense

// 使用 Suspense 实现代码分割
import { Suspense, lazy } from "react";

const HeavyComponent = lazy(() => import("./HeavyComponent"));

function App() {
  return (
    <Suspense fallback={<Loading />}>
      <HeavyComponent />
    </Suspense>
  );
}

// 使用 Suspense 处理数据获取
function UserProfile({ userId }) {
  const user = useSuspenseQuery(userQuery, { userId });

  return (
    <div>
      <h1>{user.name}</h1>
      <UserPosts userId={userId} />
    </div>
  );
}

function App() {
  return (
    <Suspense fallback={<Skeleton />}>
      <UserProfile userId={123} />
    </Suspense>
  );
}

3. 避免在 render 阶段执行副作用

// 错误:在 render 阶段修改 DOM
function BadExample() {
  const elementRef = useRef(null);

  // 错误!render 阶段不应该有副作用
  if (elementRef.current) {
    elementRef.current.style.color = "red";
  }

  return <div ref={elementRef}>内容</div>;
}

// 正确:在 useEffect 中执行副作用
function GoodExample() {
  const elementRef = useRef(null);

  useEffect(() => {
    // 在 commit 阶段执行
    if (elementRef.current) {
      elementRef.current.style.color = "red";
    }
  }, []);

  return <div ref={elementRef}>内容</div>;
}

4. 使用 startTransition 优化用户体验

import { useTransition } from "react";

function TabContainer() {
  const [activeTab, setActiveTab] = useState("home");
  const [isPending, startTransition] = useTransition();

  const switchTab = (tab) => {
    startTransition(() => {
      setActiveTab(tab);
    });
  };

  return (
    <div>
      <TabBar
        activeTab={activeTab}
        onSwitch={switchTab}
        isPending={isPending}
      />
      {isPending && <TabSkeleton />}
      <TabContent activeTab={activeTab} />
    </div>
  );
}

面试要点

  1. Fiber 是什么: React 16 引入的新的协调引擎,解决递归渲染的卡顿问题

  2. Fiber 的核心特性:

    • 可中断的渲染(时间切片)
    • 优先级调度
    • 更好的错误处理
    • 支持并发特性
  3. Fiber 数据结构:

    • 链表结构(child、sibling、return)
    • 双缓冲(alternate)
    • 副作用标记(flags)
  4. 渲染的两个阶段:

    • Render 阶段:可中断,构建 Fiber 树
    • Commit 阶段:不可中断,执行 DOM 操作
  5. 优化手段:

    • useTransition:非紧急更新
    • useDeferredValue:延迟值
    • Suspense:异步边界
  6. 与旧架构的区别:

    • 递归 vs 循环
    • 同步 vs 可中断
    • 单一优先级 vs 多优先级