什么是 React Fiber? 为什么要引入 React Fiber?
- fiber是 react16 以后引入的一种新的协调算法
旧的直接从vodm --> 真实DOM
新的从 vdom - fiber - 真实dom (真实DOM 存到 fiber节点的stateNode),真实DOM里面也可以通过internalInstanceKey取fiber
-
在旧的协调算法中react会从根组件开始渲染,并且这个渲染过程是不可以暂停的,如果有某些渲染任务会时间过长会占用主线程导致页面卡顿
-
所以引入了fiber,它是一种链表结构,会把渲染过程拆分成多个小的工作单元,并且每一个fiber节点会存储(return、child、sibling的指针),使渲染过程可以中断、暂停、恢复,避免,提高应用的响应性能,在每个任务完成后,会检查是否有更高优先级的任务需要处理
React Fiber 是如何工作的?
-
调和阶段(reconcile Phase)
1.这个阶段是可中断的,React 会将整个渲染任务拆分成多个小的工作单元(Fiber 节点)
2.按照深度优先的顺序遍历 Fiber 树,为每个 Fiber 节点执行
beginWork和completeWork方法3.构建新的 Fiber 树(
workInProgress树)。在这个过程中,如果浏览器有更高优先级的任务需要处理,渲染可以被暂停
用while 循环执行
performUnitOfWork
beginWork作用:
调用
renderWithHooks,处理hooks
- 有老
fiber并且有老的hook链表fiber.memoizedState- 否则:
mountXXXHOOK初始化hooks链表,存到memoizedState,并且返回dispatch触发入队更新的函数调用
reconcileChildren,根据当前Fiber节点去构建子Fiber链表
首次渲染是构建
fiber,第一个Fiber节点就是Root Fiber(存储着vdom)用于构建新的 Fiber 子链表,fiber pendingProps存储的就是vdom props,每次取里面children构建子链表`有
老Fiber的话,做DOM-DIFF拿老的子fiber链表和新的子虚拟DOM进行比较 ,进行最小化的更新
completeWork作用:
根据 Fiber 节点
创建真实 DOM,挂载所有子节点,将DOM存储到stateNode中更新阶段
diffProperties对比新旧属性,生成更新队列
标记副作用,合并子节点的副作用到subtreeFlags
-
提交阶段(Commit Phase)
1.此阶段是不可中断的,从
alternate取出最新构建出的fiber树,执行副作用,修改真实 DOM2.检查并处理
subtreeFlags(子 fiber 的副作用)和自身的flags,完成 DOM 的插入、更新等操作3.
updateQueue更新effect3.完成页面的更新。
遍历
workInProgress树,根据节点的flags属性执行相应的副作用操作
什么是双缓存机制?
react 存在两棵 Fiber 树:current 树和 workInProgress 树
current树:对应页面上当前显示的真实 DOM,代表已经渲染完成的 Fiber 树。
workInProgress树: 正在构建中的新 Fiber 树,基于current树和新的虚拟 DOM 创建。在渲染阶段,React 会在workInProgress树上进行操作,当所有的更新完成后,workInProgress树会变成新的
React Fiber 是如何实现可中断渲染的?
因为fiber是链表结构,中断后可以通过一个全局变量nextUnitOfWork存储fiber节点,并根据节点的child、parent、sibling 继续执行任务
如何实现时间分片
通过模拟浏览器的requestIdleCallbackapi,在浏览器空闲时段处理任务,每个工作单元执行后检查剩余时间,中断低优先级任务时,保存其进度,后续恢复或重新开始
不适用浏览器api原因:兼容差
react hooks 链表
函数组件
1.首次渲染时:React 会初始化一个链表来管理 Hooks。每个 Hook 都会被表示为链表中的一个节点,节点包含了 Hook 的状态、更新队列等信息。
2.渲染时:会根据链表结构遍历每个 Hook 节点,处理更新操作和副作用逻辑
react 怎么触发渲染 调度更新
举例:useState/useReducer的 setState | dispatch
如果setState触发更新则创建一个更新对象Update = { action, next, lane }
然后入队concurrentQueues = [fiber, queue, update, lane]这个并发队列的全局变量,再根据fiber 找到root fiber调用scheduleUpdateOnFiber构建新的root fiber树
并且从全局的并发队列(concurrentQueues)中依次取出每个更新任务,并将它们插入到对应 Fiber 节点的更新队列(UpdateQueue)中
export function finishQueueingConcurrentUpdates() {
const endIndex = concurrentQueuesIndex;//9 只是一边界条件
concurrentQueuesIndex = 0;
let i = 0;
while (i < endIndex) {
const fiber = concurrentQueues[i++];
const queue = concurrentQueues[i++];
const update = concurrentQueues[i++];
const lane = concurrentQueues[i++];
if (queue !== null && update !== null) {
const pending = queue.pending;
if (pending === null) {
update.next = update;
} else {
update.next = pending.next;
pending.next = update;
}
queue.pending = update;
}
}
}
function enqueueUpdate(fiber, queue, update, lane) {
//012 setNumber1 345 setNumber2 678 setNumber3
concurrentQueues[concurrentQueuesIndex++] = fiber;//函数组件对应的fiber
concurrentQueues[concurrentQueuesIndex++] = queue;//要更新的hook对应的更新队列
concurrentQueues[concurrentQueuesIndex++] = update; //更新对象
concurrentQueues[concurrentQueuesIndex++] = lane; //更新对应的赛道
//当我们向一个fiber上添加一个更新的时候,要把此更新的赛道合并到此fiber的赛道上
fiber.lanes = mergeLanes(fiber.lanes, lane);
}
/**
* 渲染函数组件
* @param {*} current 老fiber
* @param {*} workInProgress 新fiber
* @param {*} Component 组件定义
* @param {*} props 组件属性
* @returns 虚拟DOM或者说React元素
*/
export function renderWithHooks(current, workInProgress, Component, props, nextRenderLanes) {
//当前正在渲染的车道
renderLanes = nextRenderLanes
currentlyRenderingFiber = workInProgress;
//函数组件更新队列里存的effect
workInProgress.updateQueue = null;
//函数组件状态存的hooks的链表
workInProgress.memoizedState = null;
//如果有老的fiber,并且有老的hook链表
if (current !== null && current.memoizedState !== null) {
ReactCurrentDispatcher.current = HooksDispatcherOnUpdate;
} else {
ReactCurrentDispatcher.current = HooksDispatcherOnMount;
}
//需要要函数组件执行前给ReactCurrentDispatcher.current赋值
const children = Component(props);
currentlyRenderingFiber = null;
workInProgressHook = null;
currentHook = null;
renderLanes = NoLanes;
return children;
}
react useEffect
调用useEffect会创建一个effect对象,存储到fiber updateQueue,形成一个环形链表
const effect = {
tag, // 类型标记
create, // 副作用函数
destroy, // 清理函数
deps, // 依赖项数组
next: null // 指向下一个 Effect 的指针
};
fiber结构
// 一个典型的 Fiber 节点结构
const fiber = {
// 基础信息
tag: FunctionComponent, // Fiber 类型(如 FunctionComponent、HostComponent、ClassComponent)
key: 'list-item', // 唯一标识(用于 Diffing 算法)
type: MyComponent, // 组件类型(函数/类组件本身,或 DOM 标签名如 'div')
elementType: MyComponent, // 与 type 类似,但对某些高阶组件有差异
stateNode: divElement, // 对应的真实 DOM 节点(HostComponent)或组件实例(ClassComponent)
// 树结构指针
return: parentFiber, // 父节点
child: firstChildFiber, // 第一个子节点
sibling: nextSiblingFiber,// 下一个兄弟节点
index: 0, // 在父节点的子节点中的位置
// 副作用相关
flags: Placement | Update,// 标记需要执行的副作用(如插入、更新、删除)
subtreeFlags: NoFlags, // 子树的副作用标记(优化遍历)
deletions: [childFiber], // 待删除的子节点(用于 Commit 阶段)
updateQueue: { // 更新队列(如状态更新、Effect)
lastEffect: effect1, // 环形链表尾指针(管理 useEffect、useLayoutEffect)
stores: [], // Context 相关存储
},
memoizedState: { // Hooks 链表(useState、useEffect 等的状态)
memoizedState: 'value', // 当前状态值
next: nextHook, // 下一个 Hook
queue: { // 状态更新队列(如 setState 的多次调用)
pending: update1, // 环形更新链表
},
},
memoizedProps: { text: 'Hello' }, // 上次渲染的 Props
pendingProps: { text: 'World' }, // 待处理的 Props(来自本次更新)
// 调度相关
lanes: DefaultLane, // 当前 Fiber 的优先级(Lane 模型)
childLanes: NoLanes, // 子节点的优先级
alternate: alternateFiber,// 双缓冲中的另一棵树节点(current/workInProgress)
// 其他
mode: ConcurrentMode, // 渲染模式(ConcurrentMode、BlockingMode 等)
ref: refObject, // Ref 对象(如 createRef 的引用)
};