说说对 React 的 Fiber 架构的理解?
问题解析(面试官考察点)
面试官通过此问题主要考察:
- 对 React Fiber 架构设计目标的理解
- 对 Fiber 数据结构的了解
- 对 React 渲染阶段和提交阶段分离的理解
- 对时间切片和优先级调度的认识
- 对 Fiber 如何解决递归渲染卡顿问题的理解
核心概念(基础知识点)
什么是 Fiber
Fiber 是 React 16 引入的新的协调引擎(Reconciliation Engine),主要目标是:
- 可中断的渲染: 将渲染工作拆分成小单元,可暂停和恢复
- 优先级调度: 根据任务优先级决定执行顺序
- 更好的错误处理: 支持错误边界(Error Boundaries)
- 为并发特性铺路: 支持 Suspense、Concurrent Mode 等特性
为什么需要 Fiber
在 React 15 及之前,协调过程是递归执行的,存在以下问题:
- 阻塞主线程: 大量组件更新时,浏览器无法响应用户输入
- 无法中断: 一旦开始渲染,必须等到完成才能停止
- 优先级单一: 所有更新一视同仁,无法优先处理重要更新
详细解答(代码示例)
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>
);
}
面试要点
-
Fiber 是什么: React 16 引入的新的协调引擎,解决递归渲染的卡顿问题
-
Fiber 的核心特性:
- 可中断的渲染(时间切片)
- 优先级调度
- 更好的错误处理
- 支持并发特性
-
Fiber 数据结构:
- 链表结构(child、sibling、return)
- 双缓冲(alternate)
- 副作用标记(flags)
-
渲染的两个阶段:
- Render 阶段:可中断,构建 Fiber 树
- Commit 阶段:不可中断,执行 DOM 操作
-
优化手段:
- useTransition:非紧急更新
- useDeferredValue:延迟值
- Suspense:异步边界
-
与旧架构的区别:
- 递归 vs 循环
- 同步 vs 可中断
- 单一优先级 vs 多优先级