React Fiber是React的核心架构,它基于链表结构组织和管理组件树。本文将从链表基础开始,逐步深入探讨Fiber节点、Fiber树和双缓冲机制,以及它们之间的关系。
1. 链表基础
在深入Fiber架构之前,我们需要先理解链表这一基础数据结构。链表是由一系列节点组成的线性集合,每个节点包含数据和指向下一个节点的指针。
1.1 链表的基本操作及复杂度分析
以下是链表的基本操作实现及其时间复杂度分析:
class Node {
constructor(data) {
this.data = data
this.next = null
}
}
class LinkedList {
constructor() {
this.head = null
}
// 在链表末尾添加节点 - O(n)
append(data) {
const newNode = new Node(data)
if (!this.head) {
this.head = newNode
return
}
let current = this.head
while (current.next) {
current = current.next
}
current.next = newNode
}
// 在链表开头插入节点 - O(1)
prepend(data) {
const newNode = new Node(data)
newNode.next = this.head
this.head = newNode
}
// 删除指定数据的节点 - O(n)
delete(data) {
if (!this.head) return
if (this.head.data === data) {
this.head = this.head.next
return
}
let current = this.head
while (current.next) {
if (current.next.data === data) {
current.next = current.next.next
return
}
current = current.next
}
}
// 遍历链表 - O(n)
traverse() {
let current = this.head
while (current) {
console.log(current.data)
current = current.next
}
}
}
复杂度分析:
- 插入操作:
- 在头部插入(prepend): O(1)
- 在尾部插入(append): O(n)
- 删除操作: 平均O(n)
- 查找操作: O(n)
链表的主要优势在于插入和删除操作的灵活性,特别是在已知位置的插入和删除可以达到O(1)的时间复杂度。
2. Fiber节点结构
Fiber节点是React内部用来表示组件的核心数据结构。每个Fiber节点对应一个React元素,包含了该元素的类型、属性、状态等信息。Fiber节点构成了一个双向链表树结构,这种结构允许React实现可中断的渲染过程。
2.1 Fiber节点的完整结构
以下是Fiber节点的完整结构:
function FiberNode(
tag: WorkTag,
pendingProps: mixed,
key: null | string,
mode: TypeOfMode,
) {
// Instance
this.tag = tag;
this.key = key;
this.elementType = null;
this.type = null;
this.stateNode = null;
// Fiber
this.return = null;
this.child = null;
this.sibling = null;
this.index = 0;
this.ref = null;
this.pendingProps = pendingProps;
this.memoizedProps = null;
this.updateQueue = null;
this.memoizedState = null;
this.dependencies = null;
this.mode = mode;
// Effects
this.flags = NoFlags;
this.subtreeFlags = NoFlags;
this.deletions = null;
this.lanes = NoLanes;
this.childLanes = NoLanes;
this.alternate = null;
// ... 性能计时相关代码(省略)
// ... 开发环境相关代码(省略)
}
2.2 Fiber节点属性解释
Instance 相关
tag: 标识 Fiber 节点类型(如函数组件、类组件、原生 DOM 等)key: 用于优化更新过程的唯一标识符elementType: 元素的类型(如对于 DOM 元素是字符串,对于组件是函数或类)type: 与 elementType 类似,但可能会被特殊处理(如在懒加载时)stateNode: 保存与 Fiber 相关的本地状态(如对于 DOM 元素,它是实际的 DOM 节点)
Fiber 树结构
return: 指向父 Fiber 节点child: 指向第一个子 Fiber 节点sibling: 指向下一个兄弟 Fiber 节点index: 在父节点的子节点列表中的索引
数据相关
ref: 如果元素定义了 ref,这里存储 ref 的值pendingProps: 新的待处理的 propsmemoizedProps: 上一次渲染时的 propsupdateQueue: 存储状态更新的队列memoizedState: 上一次渲染时的 statedependencies: 依赖项(如 context、事件等)
模式与副作用
mode: 标识当前 Fiber 树的渲染模式(如 Concurrent、Strict 等)flags: 标记需要执行的副作用(如需要插入、更新、删除等)subtreeFlags: 子树中的副作用标记deletions: 需要删除的子节点列表
调度相关
lanes: 表示更新的优先级childLanes: 子树中的优先级
其他
alternate: 指向内存中的另一个 Fiber,用于双缓冲
2.3 创建Fiber节点的源码
React 使用 createFiberFromElement 函数来创建 Fiber 节点:
function createFiberFromElement(element: ReactElement, mode: TypeOfMode, lanes: Lanes): Fiber {
let owner = null;
if (__DEV__) {
owner = element._owner;
}
const type = element.type;
const key = element.key;
const pendingProps = element.props;
const fiber = createFiberFromTypeAndProps(
type,
key,
pendingProps,
owner,
mode,
lanes,
);
if (__DEV__) {
fiber._debugSource = element._source;
fiber._debugOwner = element._owner;
}
return fiber;
}
这个函数根据 React 元素创建对应的 Fiber 节点,处理元素的类型、key、props 等信息,并创建一个新的 Fiber 对象。
2.4 深入理解React元素
React元素是描述UI中一部分的普通JavaScript对象。它们是React应用的最小构建块,用于表示界面上应该渲染的内容。
2.4.1 React元素的结构
一个典型的React元素对象结构如下:
const element = {
type: 'div',
props: {
className: 'container',
children: [
{ type: 'h1', props: { children: 'Hello, World!' } },
{ type: 'p', props: { children: 'This is a paragraph.' } }
]
},
key: null,
ref: null,
$$typeof: Symbol.for('react.element'),
_owner: null,
_store: {}
}
2.4.2 React元素的主要属性
type: 指定了React元素的类型。它可以是:- 字符串(如 'div', 'span')表示原生DOM元素
- 函数或类表示自定义React组件
- React内置组件(如React.Fragment)
props: 包含了传递给组件的所有属性,包括:- 常规属性(如className, style等)
- 特殊属性children,表示子元素
key: 用于在列表中唯一标识元素,帮助React高效地更新DOMref: 用于获取对DOM节点或组件实例的引用$$typeof: 一个Symbol,用于标识这是一个React元素,防止XSS攻击_owner: 内部使用,指向创建该元素的组件
2.4.3 创建React元素
通常,我们使用JSX来创建React元素:
const element = (
<div className="container">
<h1>Hello, World!</h1>
<p>This is a paragraph.</p>
</div>
)
JSX会被Babel等工具转译为React.createElement()调用:
const element = React.createElement(
'div',
{ className: 'container' },
React.createElement('h1', null, 'Hello, World!'),
React.createElement('p', null, 'This is a paragraph.')
)
2.4.4 React元素与Fiber节点的关系
- React元素是描述UI的静态结构,而Fiber节点是React内部用于管理组件树和执行更新的动态数据结构。
- 每个React元素最终都会对应到一个Fiber节点。
3. Fiber树结构
Fiber树是由Fiber节点构成的树形结构,它反映了React组件的层次关系。Fiber树使用三个主要指针构建链表结构:
child: 指向第一个子节点sibling: 指向下一个兄弟节点return: 指向父节点
3.1 Fiber树的构建过程
以下是一个简化的Fiber树构建过程:
function createFiberFromElement(element) {
const { type, props } = element
const fiber = new FiberNode(getFiberTag(type), props, element.key, NoMode)
fiber.type = type
return fiber
}
function reconcileChildrenArray(returnFiber, currentFirstChild, newChildren) {
let prevSibling = null
let oldFiber = currentFirstChild
let newFiber = null
let index = 0
for (; index < newChildren.length; index++) {
const newChild = newChildren[index]
if (shouldTrackSideEffects && oldFiber && newChild && !sameNode(newChild, oldFiber)) {
deleteChild(returnFiber, oldFiber)
}
if (oldFiber) {
oldFiber = oldFiber.sibling
}
newFiber = createFiberFromElement(newChild)
newFiber.return = returnFiber
if (index === 0) {
returnFiber.child = newFiber
} else {
prevSibling.sibling = newFiber
}
prevSibling = newFiber
}
}
3.2 实际代码例子
考虑以下React组件结构:
function App() {
return (
<div>
<Header />
<Main>
<Article />
<Sidebar />
</Main>
<Footer />
</div>
)
}
对应的Fiber树结构可能如下:
const AppFiber = {
tag: FunctionComponent,
type: App,
child: DivFiber
// ...其他属性
}
const DivFiber = {
tag: HostComponent,
type: 'div',
return: AppFiber,
child: HeaderFiber
// ...其他属性
}
const HeaderFiber = {
tag: FunctionComponent,
type: Header,
return: DivFiber,
sibling: MainFiber
// ...其他属性
}
const MainFiber = {
tag: FunctionComponent,
type: Main,
return: DivFiber,
child: ArticleFiber,
sibling: FooterFiber
// ...其他属性
}
const ArticleFiber = {
tag: FunctionComponent,
type: Article,
return: MainFiber,
sibling: SidebarFiber
// ...其他属性
}
const SidebarFiber = {
tag: FunctionComponent,
type: Sidebar,
return: MainFiber
// ...其他属性
}
const FooterFiber = {
tag: FunctionComponent,
type: Footer,
return: DivFiber
// ...其他属性
}
这个例子展示了Fiber节点如何通过child、sibling和return指针相互连接,形成一个完整的树结构。
4. 双缓冲机制
React Fiber 架构中的双缓冲机制是优化渲染性能的关键策略。
4.1 双缓冲的核心概念
- current 树: 当前在屏幕上显示的 UI 对应的 Fiber 树。
- workInProgress 树: 用于构建下一个状态的 Fiber 树。
每个 Fiber 节点都有一个 alternate 属性,指向另一棵树中对应的节点。
4.2 双缓冲的创建和更新流程
初始渲染:
- React 创建 current 树。
- 同时创建一个对应的 workInProgress 树,但不使用。
更新过程:
- 当有更新时,React 不会创建新的 workInProgress 树。
- 而是重用已存在的 workInProgress 树,基于 current 树进行更新。
树的切换:
- 更新完成后,workInProgress 树变为新的 current 树。
- 原来的 current 树变为新的 workInProgress 树,等待下次更新。
4.3 源码实现
React 中的 createWorkInProgress 函数展示了这一机制:
function createWorkInProgress(current: Fiber, pendingProps: any): Fiber {
let workInProgress = current.alternate;
if (workInProgress === null) {
// 首次渲染时创建 workInProgress
workInProgress = createFiber(
current.tag,
pendingProps,
current.key,
current.mode,
);
workInProgress.elementType = current.elementType;
workInProgress.type = current.type;
workInProgress.stateNode = current.stateNode;
workInProgress.alternate = current;
current.alternate = workInProgress;
} else {
// 后续更新时重用已存在的 workInProgress
workInProgress.pendingProps = pendingProps;
workInProgress.type = current.type;
workInProgress.flags = NoFlags;
// ... 重置其他属性
}
// 复制必要的属性
workInProgress.child = current.child;
workInProgress.memoizedProps = current.memoizedProps;
workInProgress.memoizedState = current.memoizedState;
workInProgress.updateQueue = current.updateQueue;
// ... 复制其他属性
return workInProgress;
}
4.4 实际例子:局部替换和双缓存
让我们通过一个具体的例子来说明 React 如何使用双缓存和局部替换来更新 UI。
假设我们有以下组件结构:
function App() {
const [count, setCount] = useState(0)
return (
<div>
<Header />
<Main count={count} />
<Footer />
</div>
)
}
function Main({ count }) {
return (
<div>
<h2>Count: {count}</h2>
<button onClick={() => setCount(count + 1)}>Increment</button>
</div>
)
}
初始渲染后,Fiber 树结构可能如下:
// current 树
const currentRoot = {
tag: HostRoot,
child: {
tag: FunctionComponent,
type: App,
child: {
tag: HostComponent,
type: 'div',
child: {
tag: FunctionComponent,
type: Header,
sibling: {
tag: FunctionComponent,
type: Main,
memoizedState: { count: 0 },
sibling: {
tag: FunctionComponent,
type: Footer
}
}
}
}
}
}
// workInProgress 树(初始为 null)
let workInProgressRoot = null
当用户点击 "Increment" 按钮时,React 会触发更新。这时,React 会创建或重用 workInProgress 树:
// 更新过程中的 workInProgress 树
workInProgressRoot = {
tag: HostRoot,
child: {
tag: FunctionComponent,
type: App,
child: {
tag: HostComponent,
type: 'div',
child: {
tag: FunctionComponent,
type: Header,
sibling: {
tag: FunctionComponent,
type: Main,
memoizedState: { count: 1 }, // 注意这里的状态更新
sibling: {
tag: FunctionComponent,
type: Footer
}
}
}
}
}
}
在这个过程中,React 只需要更新 Main 组件对应的 Fiber 节点,其他节点可以直接复用 current 树中的节点。这就是局部替换的体现。
更新完成后,React 会交换 current 和 workInProgress 的引用:
// 交换引用
const temp = currentRoot
currentRoot = workInProgressRoot
workInProgressRoot = temp
这样,新的 current 树就反映了更新后的 UI 状态,而旧的 current 树成为了新的 workInProgress 树,等待下一次更新。
这个例子展示了 React 如何通过双缓存机制和局部替换来高效地管理更新。它只修改必要的部分,复用大部分现有结构。