React Fiber构建源码解析
引言
前面的章节,我们在render方法里,简单涉及到了fiber,那么:
- fiber究竟是什么?
- fiber的出现解决了什么问题?
- 为什么fiber之前的版本,没办法解决
下面,我们带着这些问题,来仔细聊聊Fiber
一. Fiber是什么
在fiber出现之前,react的架构体系只有协调器reconciler和渲染器render。
当前有新的update时,react会递归所有的vdom节点,如果dom节点过多,会导致其他事件影响滞后,造成卡顿。即之前的react版本无法中断工作过程,一旦递归开始无法停留下来。
为了解决这一系列问题,react历时多年重构了底层架构,引入了fiber。 fiber的出现使得react能够异步可中断工作任务,并且可以在浏览器空闲时,从中断处继续往下工作。 当出现多个高优先任务并行时,react引入lane模型取代之前的expireTime机制。
这里提及下vue工作原理,为什么有新的update任务时,vue不需要做全量递归,而react需要?(留个悬念,大家可以先思考下)
fiber本质上是一种数据结构,在react17后,没有vdom概念,一切皆是Fiber,但Fiber != vdom。
二. FiberRoot
FiberRoot是react启动阶段,要构建的fiber对象。与之容易混淆是rootFiber,下面会具体介绍。
fiberRoot生成
function createFiberRoot(containerInfo, tag, hydrate, hydrationCallbacks) { var root = new FiberRootNode(containerInfo, tag, hydrate); // stateNode is any. var uninitializedFiber = createHostRootFiber(tag); root.current = uninitializedFiber; uninitializedFiber.stateNode = root; initializeUpdateQueue(uninitializedFiber); return root; }
fiberRoot类
function FiberRootNode(containerInfo, tag, hydrate) { this.tag = tag; this.containerInfo = containerInfo; this.pendingChildren = null; this.current = null; this.pingCache = null; this.finishedWork = null; this.timeoutHandle = noTimeout; this.context = null; this.pendingContext = null; this.hydrate = hydrate; this.callbackNode = null; this.callbackPriority = NoLanePriority; this.eventTimes = createLaneMap(NoLanes); this.expirationTimes = createLaneMap(NoTimestamp); this.pendingLanes = NoLanes; this.suspendedLanes = NoLanes; this.pingedLanes = NoLanes; this.expiredLanes = NoLanes; this.mutableReadLanes = NoLanes; this.finishedLanes = NoLanes; this.entangledLanes = NoLanes; this.entanglements = createLaneMap(NoLanes); { this.mutableSourceEagerHydrationData = null; } { this.interactionThreadID = unstable_getThreadID(); this.memoizedInteractions = new Set(); this.pendingInteractionMap = new Map(); } { switch (tag) { case BlockingRoot: this._debugRootType = 'createBlockingRoot()'; break; case ConcurrentRoot: this._debugRootType = 'createRoot()'; break; case LegacyRoot: this._debugRootType = 'createLegacyRoot()'; break; } } }
fiberRoot本质上fiber的顶层对象,其中tag记录了几种启动模式:
- 0普通模式
- 1 小部分并发模式
- 2 并发模式
启动模式的不同,在后协调阶段有具体差异。
该类引用的实例,即current对象是rootFiber。finishedWork是fiber完成协调器work之后的结果,下面有许多字段都带有lane,这里可以先不关注,后面章节我们单独聊聊Lane模型
三. RootFiber
rootFiber生成
function createFiberRoot(containerInfo, tag, hydrate, hydrationCallbacks) { var root = new FiberRootNode(containerInfo, tag, hydrate); // stateNode is any. var uninitializedFiber = createHostRootFiber(tag); root.current = uninitializedFiber; uninitializedFiber.stateNode = root; initializeUpdateQueue(uninitializedFiber); return root; }
createHostRootFiber
function createHostRootFiber(tag) { var mode; if (tag === ConcurrentRoot) { mode = ConcurrentMode | BlockingMode | StrictMode; } else if (tag === BlockingRoot) { mode = BlockingMode | StrictMode; } else { mode = NoMode; } if ( isDevToolsPresent) { // Always collect profile timings when DevTools are present. // This enables DevTools to start capturing timing at any point– // Without some nodes in the tree having empty base times. mode |= ProfileMode; } return createFiber(HostRoot, null, null, mode); }
FiberNode
function FiberNode(tag, pendingProps, key, mode) { this.tag = tag; this.key = key; this.elementType = null; this.type = null; this.stateNode = null; 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.nextEffect = null; this.firstEffect = null; this.lastEffect = null; this.lanes = NoLanes; this.childLanes = NoLanes; this.alternate = null; { this.actualDuration = Number.NaN; this.actualStartTime = Number.NaN; this.selfBaseDuration = Number.NaN; this.treeBaseDuration = Number.NaN; // It's okay to replace the initial doubles with smis after initialization. this.actualDuration = 0; this.actualStartTime = -1; this.selfBaseDuration = 0; this.treeBaseDuration = 0; } { // This isn't directly used but is handy for debugging internals: this._debugID = debugCounter++; this._debugSource = null; this._debugOwner = null; this._debugNeedsRemount = false; this._debugHookTypes = null; if (!hasBadMapPolyfill && typeof Object.preventExtensions === 'function') { Object.preventExtensions(this); } } }
到这里,fiberNode是一个类,往下的所有dom都将实例化这个类。
这里的tag依旧是启动模式,return是父节点fiber,child是子节点第一个fiber,sibling是兄弟节点的fiber。
另外,flags很重要,在后续work阶段会大量使用,另外flags和lane都是二进制数据对象,后面大量运用位运算。
effect对象,会在work loop阶段生成,也就是副作用,比如我们写的useEffect,都在work lopp阶段被挂载。
每个fiber的stateNode指向具体实例节点。
flags
export type Flags = number; export const NoFlags = /* */ 0b000000000000000000; export const PerformedWork = /* */ 0b000000000000000001; export const Placement = /* */ 0b000000000000000010; export const Update = /* */ 0b000000000000000100; export const PlacementAndUpdate = /* */ 0b000000000000000110; export const Deletion = /* */ 0b000000000000001000; export const ContentReset = /* */ 0b000000000000010000; export const Callback = /* */ 0b000000000000100000; export const DidCapture = /* */ 0b000000000001000000; export const Ref = /* */ 0b000000000010000000; export const Snapshot = /* */ 0b000000000100000000; export const Passive = /* */ 0b000000001000000000; // TODO (effects) Remove this bit once the new reconciler is synced to the old. export const PassiveUnmountPendingDev = /* */ 0b000010000000000000; export const Hydrating = /* */ 0b000000010000000000; export const HydratingAndUpdate = /* */ 0b000000010000000100; // Passive & Update & Callback & Ref & Snapshot export const LifecycleEffectMask = /* */ 0b000000001110100100; // Union of all host effects export const HostEffectMask = /* */ 0b000000011111111111; // These are not really side effects, but we still reuse this field. export const Incomplete = /* */ 0b000000100000000000; export const ShouldCapture = /* */ 0b000001000000000000; export const ForceUpdateForLegacySuspense = /* */ 0b000100000000000000; export const PassiveStatic = /* */ 0b001000000000000000; export const BeforeMutationMask = /* */ 0b000000001100001010; export const MutationMask = /* */ 0b000000010010011110; export const LayoutMask = /* */ 0b000000000010100100; export const PassiveMask = /* */ 0b000000001000001000; export const StaticMask = /* */ 0b001000000000000000; export const MountLayoutDev = /* */ 0b010000000000000000; export const MountPassiveDev = /* */ 0b100000000000000000;
lane
export const NoLanes: Lanes = /* */ 0b0000000000000000000000000000000; export const NoLane: Lane = /* */ 0b0000000000000000000000000000000; export const SyncLane: Lane = /* */ 0b0000000000000000000000000000001; export const SyncBatchedLane: Lane = /* */ 0b0000000000000000000000000000010; export const InputDiscreteHydrationLane: Lane = /* */ 0b0000000000000000000000000000100; const InputDiscreteLanes: Lanes = /* */ 0b0000000000000000000000000011000; const InputContinuousHydrationLane: Lane = /* */ 0b0000000000000000000000000100000; const InputContinuousLanes: Lanes = /* */ 0b0000000000000000000000011000000; export const DefaultHydrationLane: Lane = /* */ 0b0000000000000000000000100000000; export const DefaultLanes: Lanes = /* */ 0b0000000000000000000111000000000; const TransitionHydrationLane: Lane = /* */ 0b0000000000000000001000000000000; const TransitionLanes: Lanes = /* */ 0b0000000001111111110000000000000; const RetryLanes: Lanes = /* */ 0b0000011110000000000000000000000; export const SomeRetryLane: Lanes = /* */ 0b0000010000000000000000000000000; export const SelectiveHydrationLane: Lane = /* */ 0b0000100000000000000000000000000; const NonIdleLanes = /* */ 0b0000111111111111111111111111111; export const IdleHydrationLane: Lane = /* */ 0b0001000000000000000000000000000; const IdleLanes: Lanes = /* */ 0b0110000000000000000000000000000; export const OffscreenLane: Lane = /* */ 0b1000000000000000000000000000000;
关于flags和lane,我们先有个感性认知,后面章节单独分析
initializeUpdateQueue
function initializeUpdateQueue(fiber) { var queue = { baseState: fiber.memoizedState, firstBaseUpdate: null, lastBaseUpdate: null, shared: { pending: null }, effects: null }; fiber.updateQueue = queue; }
rootFiber上调用initializeUpdateQueue,初始化queue对象,这里仅仅是初始化对象而已,并不是许多文章说fiber加入更新队列。fiber的更新队列和这里没有任何关系,fiber的更新队列是后续schedule调度的task queue。
四. Root
root对象是legacyCreateRootFromDOMContainer方法的返回对象,这个对象是全局唯一,并贯穿了react后续的各阶段计算。
至此,我们对应fiber有个感性的认知。另外需要说明的是,每个dom节点都是fiber,fiber通过return, child, sibling关联其他fiber,本质上fiber是个链表数据结构,这一点和后续的effect数据结构还是有区别的。
在root生成后,首次初始化应用,将进入核心updateContainer方法
updateContainer(children, fiberRoot, parentComponent, callback);
updateContainer
function updateContainer(element, container, parentComponent, callback) { // ...省略eventTime和lane相关,后续单独介绍 var update = createUpdate(eventTime, lane); // Caution: React DevTools currently depends on this property // being called "element". update.payload = { element: element }; callback = callback === undefined ? null : callback; if (callback !== null) { { if (typeof callback !== 'function') { error('render(...): Expected the last optional `callback` argument to be a ' + 'function. Instead received: %s.', callback); } } update.callback = callback; } enqueueUpdate(current$1, update); scheduleUpdateOnFiber(current$1, lane, eventTime); return lane; }
createUpdate
function createUpdate(eventTime, lane) { var update = { eventTime: eventTime, lane: lane, tag: UpdateState, payload: null, callback: null, next: null }; return update; }
function enqueueUpdate(fiber, update) { var updateQueue = fiber.updateQueue; if (updateQueue === null) { // Only occurs if the fiber has been unmounted. return; } var sharedQueue = updateQueue.shared; var pending = sharedQueue.pending; if (pending === null) { // This is the first update. Create a circular list. update.next = update; } else { update.next = pending.next; pending.next = update; } sharedQueue.pending = update; }
在rootFiber上,创建更新对象,并挂载至enqueueUpdate上。
update对象上payload很重要,后面在协调阶段-fiber树构建阶段是重要的输入。
update对象也是链表结构,通过next关联下一个update对象。
至此,fiber的初始化对象到这里就结束了,目前内存中只存在fiberRoot和rootFiber对象,下面调的scheduleUpdateOnFiber方法,将正式进入协调阶段,更多关于React构建Fiber的资料请关注脚本之家其它相关文章!
相关文章
react-router v4如何使用history控制路由跳转详解
这篇文章主要给大家介绍了关于react-router v4如何使用history控制路由跳转的相关资料,文中通过示例代码介绍的的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧。2018-01-01如何应用 SOLID 原则在 React 中整理代码之开闭原则
React 不是面向对象,但这些原则背后的主要思想可能是有帮助的,在本文中,我将尝试演示如何应用这些原则来编写更好的代码,对React SOLID原则开闭原则相关知识感兴趣的朋友一起看看吧2022-07-07
最新评论