本人系一个惯用Vue的菜鸡,恰巧周末和大佬扯蛋,峰回路转谈到了fiber,被大佬疯狂鄙视...前端
大佬还和我吐槽了如今的忘了环境node
因而本菜开始了 React Fiber 相关的读源码过程。为何看 Fiber?由于 Vue 没有,Vue3 也没有,可是却被吹的很神奇。react
本菜于编写时间于:2020/05/25
,参考的当日源码版本 v16.13.1
git
首先必需要知道为何会出现 Fibergithub
旧版本React同步更新:当React决定要加载或者更新组件树时,会作不少事,好比调用各个组件的生命周期函数,计算和比对Virtual DOM,最后更新DOM树。数组
举个栗子:更新一个组件须要1毫秒,若是要更新1000个组件,那就会耗时1秒
,在这1秒
的更新过程当中,主线程都在专心运行更新操做。浏览器
而浏览器每间隔必定的时间从新绘制一下当前页面。通常来讲这个频率是每秒60次。也就是说每16毫秒( 1 / 60 ≈ 0.0167 )浏览器会有一个周期性地重绘行为,这每16毫秒咱们称为一帧。这一帧的时间里面浏览器作些什么事情呢:安全
若是这六个步骤中,任意一个步骤所占用的时间过长,总时间超过 16ms 了以后,用户也许就能看到卡顿。而上述栗子中组件同步更新耗时 1秒
,意味着差很少用户卡顿了 1秒钟!!!(差很少 - -!)bash
由于JavaScript单线程的特色,每一个同步任务不能耗时太长,否则就会让程序不会对其余输入做出相应,React的更新过程就是犯了这个禁忌,而React Fiber就是要改变现状。数据结构
解决同步更新的方案之一就是时间切片:把更新过程碎片化,把一个耗时长的任务分红不少小片。执行非阻塞渲染,基于优先级应用更新以及在后台预渲染内容。
Fiber 就是由 performUnitOfWork
(ps:后文详细讲述) 方法操控的 工做单元,做为一种数据结构,用于表明某些worker,换句话说,就是一个work单元,经过Fiber的架构,提供了一种跟踪,调度,暂停和停止工做的便捷方式。
Fiber的建立和使用过程:
workInProgressTree
用于计算更新(双缓冲),能够认为是一颗表示当前工做进度的树。还有一颗表示已渲染界面的旧树,React就是一边和旧树比对,一边构建WIP树的。 alternate
指向旧树的同等节点。PS:上文说的 workInProgress
属于 beginWork
流程了,若是要写下来差很少篇幅还会增长一倍,这就不详细说明了...(主要是本人懒又菜...)
Fiber的体系结构分为两个主要阶段:reconciliation
(协调)/render 和 commit
,
Reconciliation 阶段在 Fiber重构后 和旧版本思路差异不大, 只不过不会再递归去比对、并且不会立刻提交变动。
涉及生命钩子
reconciliation
特性:
完成 reconciliation 过程。这里用的是 深度优先搜索(DFS)
,先处理子节点,再处理兄弟节点,直到循环完成。
涉及生命钩子
render
和 commit
:不能暂停,会一直更新界面直到完成
对于UI来讲须要考虑如下问题:
并非全部的state更新都须要当即显示出来,好比:
因此,React 定义了一系列事件优先级
下面是优先级时间的源码
[源码文件](github.com/facebook/re…
var maxSigned31BitInt = 1073741823;
// Times out immediately
var IMMEDIATE_PRIORITY_TIMEOUT = -1;
// Eventually times out
var USER_BLOCKING_PRIORITY = 250;
var NORMAL_PRIORITY_TIMEOUT = 5000;
var LOW_PRIORITY_TIMEOUT = 10000;
// Never times out
var IDLE_PRIORITY = maxSigned31BitInt;
复制代码
当有更新任务来的时候,不会立刻去作 Diff 操做,而是先把当前的更新送入一个 Update Queue 中,而后交给 Scheduler
去处理,Scheduler 会根据当前主线程的使用状况去处理此次 Update。
无论执行的过程怎样拆分、以什么顺序执行,Fiber 都会保证状态的一致性和视图的一致性。
如何保证相同在必定时间内触发的优先级同样的任务到期时间相同? React 经过 ceiling
方法来实现的。。。本菜没使用过 |
语法...
下面是处理到期时间的 ceiling
源码
[源码文件](github.com/facebook/re…
function ceiling(num, precision) {
return (((num / precision) | 0) + 1) * precision;
}
复制代码
那么为何须要保证时间一致性?请看下文。
首先要找到调度入口地址 scheduleUpdateOnFiber
,
每个root都有一个惟一的调度任务,若是已经存在,咱们要确保到期时间与下一级别任务的相同(因此用上文提到的 ceiling
方法来控制到期时间)
export function scheduleUpdateOnFiber( fiber: Fiber, expirationTime: ExpirationTime, ) {
checkForNestedUpdates();
warnAboutRenderPhaseUpdatesInDEV(fiber);
// 调用markUpdateTimeFromFiberToRoot,更新 fiber 节点的 expirationTime
// ps 此时的fiber树只有一个root fiber。
const root = markUpdateTimeFromFiberToRoot(fiber, expirationTime);
if (root === null) {
warnAboutUpdateOnUnmountedFiberInDEV(fiber);
return;
}
// TODO: computeExpirationForFiber also reads the priority. Pass the
// priority as an argument to that function and this one.
// 还只是TODO
// computeExpirationForFiber还会读取优先级。
// 将优先级做为参数传递给该函数和该函数。
const priorityLevel = getCurrentPriorityLevel();
if (expirationTime === Sync) {
if (
// Check if we're inside unbatchedUpdates
// 检查是否在未批处理的更新内
(executionContext & LegacyUnbatchedContext) !== NoContext &&
// Check if we're not already rendering
// 检查是否还没有渲染
(executionContext & (RenderContext | CommitContext)) === NoContext
) {
// Register pending interactions on the root to avoid losing traced interaction data.
// 在根上注册待处理的交互,以免丢失跟踪的交互数据。
schedulePendingInteractions(root, expirationTime);
// This is a legacy edge case. The initial mount of a ReactDOM.render-ed
// root inside of batchedUpdates should be synchronous, but layout updates
// should be deferred until the end of the batch.
performSyncWorkOnRoot(root);
} else {
ensureRootIsScheduled(root);
schedulePendingInteractions(root, expirationTime);
if (executionContext === NoContext) {
// Flush the synchronous work now, unless we're already working or inside
// a batch. This is intentionally inside scheduleUpdateOnFiber instead of
// scheduleCallbackForFiber to preserve the ability to schedule a callback
// without immediately flushing it. We only do this for user-initiated
// updates, to preserve historical behavior of legacy mode.
// 推入调度任务队列
flushSyncCallbackQueue();
}
}
} else {
// Schedule a discrete update but only if it's not Sync.
if (
(executionContext & DiscreteEventContext) !== NoContext &&
// Only updates at user-blocking priority or greater are considered
// discrete, even inside a discrete event.
(priorityLevel === UserBlockingPriority ||
priorityLevel === ImmediatePriority)
) {
// This is the result of a discrete event. Track the lowest priority
// discrete update per root so we can flush them early, if needed.
if (rootsWithPendingDiscreteUpdates === null) {
rootsWithPendingDiscreteUpdates = new Map([[root, expirationTime]]);
} else {
const lastDiscreteTime = rootsWithPendingDiscreteUpdates.get(root);
if (
lastDiscreteTime === undefined ||
lastDiscreteTime > expirationTime
) {
rootsWithPendingDiscreteUpdates.set(root, expirationTime);
}
}
}
// Schedule other updates after in case the callback is sync.
ensureRootIsScheduled(root);
schedulePendingInteractions(root, expirationTime);
}
}
复制代码
上面源码主要作了如下几件事
markUpdateTimeFromFiberToRoot
更新 Fiber 节点的 expirationTime
ensureRootIsScheduled
(更新重点)schedulePendingInteractions
实际上会调用 scheduleInteractions
scheduleInteractions
会利用FiberRoot的 pendingInteractionMap
属性和不一样的 expirationTime
,获取每次schedule所需的update任务的集合,记录它们的数量,并检测这些任务是否会出错。更新的重点在于 scheduleUpdateOnFiber
每一次更新都会调用 function ensureRootIsScheduled(root: FiberRoot)
下面是 ensureRootIsScheduled
的源码
function ensureRootIsScheduled(root: FiberRoot) {
const lastExpiredTime = root.lastExpiredTime;
if (lastExpiredTime !== NoWork) {
// Special case: Expired work should flush synchronously.
root.callbackExpirationTime = Sync;
root.callbackPriority_old = ImmediatePriority;
root.callbackNode = scheduleSyncCallback(
performSyncWorkOnRoot.bind(null, root),
);
return;
}
const expirationTime = getNextRootExpirationTimeToWorkOn(root);
const existingCallbackNode = root.callbackNode;
if (expirationTime === NoWork) {
// There's nothing to work on.
if (existingCallbackNode !== null) {
root.callbackNode = null;
root.callbackExpirationTime = NoWork;
root.callbackPriority_old = NoPriority;
}
return;
}
// TODO: If this is an update, we already read the current time. Pass the
// time as an argument.
const currentTime = requestCurrentTimeForUpdate();
const priorityLevel = inferPriorityFromExpirationTime(
currentTime,
expirationTime,
);
// If there's an existing render task, confirm it has the correct priority and
// expiration time. Otherwise, we'll cancel it and schedule a new one.
if (existingCallbackNode !== null) {
const existingCallbackPriority = root.callbackPriority_old;
const existingCallbackExpirationTime = root.callbackExpirationTime;
if (
// Callback must have the exact same expiration time.
existingCallbackExpirationTime === expirationTime &&
// Callback must have greater or equal priority.
existingCallbackPriority >= priorityLevel
) {
// Existing callback is sufficient.
return;
}
// Need to schedule a new task.
// TODO: Instead of scheduling a new task, we should be able to change the
// priority of the existing one.
cancelCallback(existingCallbackNode);
}
root.callbackExpirationTime = expirationTime;
root.callbackPriority_old = priorityLevel;
let callbackNode;
if (expirationTime === Sync) {
// Sync React callbacks are scheduled on a special internal queue
callbackNode = scheduleSyncCallback(performSyncWorkOnRoot.bind(null, root));
} else if (disableSchedulerTimeoutBasedOnReactExpirationTime) {
callbackNode = scheduleCallback(
priorityLevel,
performConcurrentWorkOnRoot.bind(null, root),
);
} else {
callbackNode = scheduleCallback(
priorityLevel,
performConcurrentWorkOnRoot.bind(null, root),
// Compute a task timeout based on the expiration time. This also affects
// ordering because tasks are processed in timeout order.
{timeout: expirationTimeToMs(expirationTime) - now()},
);
}
root.callbackNode = callbackNode;
}
复制代码
上面源码 ensureRootIsScheduled
主要是根据同步/异步状态作不一样的 push 功能。
同步调度 function scheduleSyncCallback(callback: SchedulerCallback)
:
syncQueue.push(callback)
)Scheduler_scheduleCallback
)performSyncWorkOnRoot
做为 SchedulerCallback
下面是 scheduleSyncCallback
源码内容
export function scheduleSyncCallback(callback: SchedulerCallback) {
// Push this callback into an internal queue. We'll flush these either in
// the next tick, or earlier if something calls `flushSyncCallbackQueue`.
if (syncQueue === null) {
syncQueue = [callback];
// Flush the queue in the next tick, at the earliest.
immediateQueueCallbackNode = Scheduler_scheduleCallback(
Scheduler_ImmediatePriority,
flushSyncCallbackQueueImpl,
);
} else {
// Push onto existing queue. Don't need to schedule a callback because
// we already scheduled one when we created the queue.
syncQueue.push(callback);
}
return fakeCallbackNode;
}
复制代码
异步调度,异步的任务调度很简单,直接将异步任务推入调度队列(Scheduler_scheduleCallback
),会将 performConcurrentWorkOnRoot
做为 SchedulerCallback
export function scheduleCallback( reactPriorityLevel: ReactPriorityLevel, callback: SchedulerCallback, options: SchedulerCallbackOptions | void | null, ) {
const priorityLevel = reactPriorityToSchedulerPriority(reactPriorityLevel);
return Scheduler_scheduleCallback(priorityLevel, callback, options);
}
复制代码
无论同步调度仍是异步调度,都会通过 Scheduler_scheduleCallback
也就是调度的核心方法 function unstable_scheduleCallback(priorityLevel, callback, options)
,它们会有各自的 SchedulerCallback
小提示:因为下面不少代码中会使用 peek
,先插一段 peek
实现,其实就是返回数组中的第一个 或者 null
export function peek(heap: Heap): Node | null {
const first = heap[0];
return first === undefined ? null : first;
}
复制代码
下面是 Scheduler_scheduleCallback
相关源码
[源码文件](github.com/facebook/re…
// 将一个任务推入任务调度队列
function unstable_scheduleCallback(priorityLevel, callback, options) {
var currentTime = getCurrentTime();
var startTime;
var timeout;
if (typeof options === 'object' && options !== null) {
var delay = options.delay;
if (typeof delay === 'number' && delay > 0) {
startTime = currentTime + delay;
} else {
startTime = currentTime;
}
timeout =
typeof options.timeout === 'number'
? options.timeout
: timeoutForPriorityLevel(priorityLevel);
} else {
// 针对不一样的优先级算出不一样的过时时间
timeout = timeoutForPriorityLevel(priorityLevel);
startTime = currentTime;
}
// 定义新的过时时间
var expirationTime = startTime + timeout;
// 定义一个新的任务
var newTask = {
id: taskIdCounter++,
callback,
priorityLevel,
startTime,
expirationTime,
sortIndex: -1,
};
if (enableProfiling) {
newTask.isQueued = false;
}
if (startTime > currentTime) {
// This is a delayed task.
newTask.sortIndex = startTime;
// 将超时的任务推入超时队列
push(timerQueue, newTask);
if (peek(taskQueue) === null && newTask === peek(timerQueue)) {
// All tasks are delayed, and this is the task with the earliest delay.
// 当全部任务都延迟时,并且该任务是最先的任务
if (isHostTimeoutScheduled) {
// Cancel an existing timeout.
cancelHostTimeout();
} else {
isHostTimeoutScheduled = true;
}
// Schedule a timeout.
requestHostTimeout(handleTimeout, startTime - currentTime);
}
} else {
newTask.sortIndex = expirationTime;
// 将新的任务推入任务队列
push(taskQueue, newTask);
if (enableProfiling) {
markTaskStart(newTask, currentTime);
newTask.isQueued = true;
}
// Schedule a host callback, if needed. If we're already performing work,
// wait until the next time we yield.
// 执行回调方法,若是已经再工做须要等待一次回调的完成
if (!isHostCallbackScheduled && !isPerformingWork) {
isHostCallbackScheduled = true;
(flushWork);
}
}
return newTask;
}
复制代码
小提示: markTaskStart
主要起到记录的功能,对应的是 markTaskCompleted
export function markTaskStart( task: { id: number, priorityLevel: PriorityLevel, ... }, ms: number, ) {
if (enableProfiling) {
profilingState[QUEUE_SIZE]++;
if (eventLog !== null) {
// performance.now returns a float, representing milliseconds. When the
// event is logged, it's coerced to an int. Convert to microseconds to
// maintain extra degrees of precision.
logEvent([TaskStartEvent, ms * 1000, task.id, task.priorityLevel]);
}
}
}
export function markTaskCompleted( task: { id: number, priorityLevel: PriorityLevel, ... }, ms: number, ) {
if (enableProfiling) {
profilingState[PRIORITY] = NoPriority;
profilingState[CURRENT_TASK_ID] = 0;
profilingState[QUEUE_SIZE]--;
if (eventLog !== null) {
logEvent([TaskCompleteEvent, ms * 1000, task.id]);
}
}
}
复制代码
unstable_scheduleCallback
主要作了几件事
options.delay
和 options.timeout
加上 timeoutForPriorityLevel()
来得到 newTask
的 expirationTime
cancelHostTimeout
requestHostTimeout
补上 cancelHostTimeout
源码
cancelHostTimeout = function() {
clearTimeout(_timeoutID);
};
复制代码
再补上 requestHostTimeout
源码
requestHostTimeout = function(cb, ms) {
_timeoutID = setTimeout(cb, ms);
};
复制代码
而后 requestHostTimeout
的 cb
也就是 handleTimeout
是啥呢?
function handleTimeout(currentTime) {
isHostTimeoutScheduled = false;
advanceTimers(currentTime);
if (!isHostCallbackScheduled) {
if (peek(taskQueue) !== null) {
isHostCallbackScheduled = true;
requestHostCallback(flushWork);
} else {
const firstTimer = peek(timerQueue);
if (firstTimer !== null) {
requestHostTimeout(handleTimeout, firstTimer.startTime - currentTime);
}
}
}
}
复制代码
上面这个方法很重要,它主要作了下面几件事
advanceTimers
检查再也不延迟的任务,并将其添加到队列中。下面是 advanceTimers
源码
function advanceTimers(currentTime) {
// Check for tasks that are no longer delayed and add them to the queue.
let timer = peek(timerQueue);
while (timer !== null) {
if (timer.callback === null) {
// Timer was cancelled.
pop(timerQueue);
} else if (timer.startTime <= currentTime) {
// Timer fired. Transfer to the task queue.
pop(timerQueue);
timer.sortIndex = timer.expirationTime;
push(taskQueue, timer);
if (enableProfiling) {
markTaskStart(timer, currentTime);
timer.isQueued = true;
}
} else {
// Remaining timers are pending.
return;
}
timer = peek(timerQueue);
}
}
复制代码
requestHostCallback
经过 MessageChannel
的异步方法来开启任务调度 performWorkUntilDeadline
requestHostCallback
这个方法特别重要
// 经过onmessage 调用 performWorkUntilDeadline 方法
channel.port1.onmessage = performWorkUntilDeadline;
// postMessage
requestHostCallback = function(callback) {
scheduledHostCallback = callback;
if (!isMessageLoopRunning) {
isMessageLoopRunning = true;
port.postMessage(null);
}
};
复制代码
而后是同文件下的 performWorkUntilDeadline
,调用了 scheduledHostCallback
, 也就是以前传入的 flushWork
const performWorkUntilDeadline = () => {
if (scheduledHostCallback !== null) {
const currentTime = getCurrentTime();
// Yield after `yieldInterval` ms, regardless of where we are in the vsync
// cycle. This means there's always time remaining at the beginning of
// the message event.
deadline = currentTime + yieldInterval;
const hasTimeRemaining = true;
try {
const hasMoreWork = scheduledHostCallback(
hasTimeRemaining,
currentTime,
);
if (!hasMoreWork) {
isMessageLoopRunning = false;
scheduledHostCallback = null;
} else {
// If there's more work, schedule the next message event at the end
// of the preceding one.
port.postMessage(null);
}
} catch (error) {
// If a scheduler task throws, exit the current browser task so the
// error can be observed.
port.postMessage(null);
throw error;
}
} else {
isMessageLoopRunning = false;
}
// Yielding to the browser will give it a chance to paint, so we can
// reset this.
needsPaint = false;
};
复制代码
flushWork
主要的做用是调用 workLoop
去循环执行全部的任务
function flushWork(hasTimeRemaining, initialTime) {
if (enableProfiling) {
markSchedulerUnsuspended(initialTime);
}
// We'll need a host callback the next time work is scheduled.
isHostCallbackScheduled = false;
if (isHostTimeoutScheduled) {
// We scheduled a timeout but it's no longer needed. Cancel it.
isHostTimeoutScheduled = false;
cancelHostTimeout();
}
isPerformingWork = true;
const previousPriorityLevel = currentPriorityLevel;
try {
if (enableProfiling) {
try {
return workLoop(hasTimeRemaining, initialTime);
} catch (error) {
if (currentTask !== null) {
const currentTime = getCurrentTime();
markTaskErrored(currentTask, currentTime);
currentTask.isQueued = false;
}
throw error;
}
} else {
// No catch in prod codepath.
return workLoop(hasTimeRemaining, initialTime);
}
} finally {
currentTask = null;
currentPriorityLevel = previousPriorityLevel;
isPerformingWork = false;
if (enableProfiling) {
const currentTime = getCurrentTime();
markSchedulerSuspended(currentTime);
}
}
}
复制代码
workLoop
和 flushWork
在一个文件中,做用是从调度任务队列中取出优先级最高的任务,而后去执行。
还记得上文讲的 SchedulerCallback
吗?
performSyncWorkOnRoot
performConcurrentWorkOnRoot
function workLoop(hasTimeRemaining, initialTime) {
let currentTime = initialTime;
advanceTimers(currentTime);
currentTask = peek(taskQueue);
while (
currentTask !== null &&
!(enableSchedulerDebugging && isSchedulerPaused)
) {
if (
currentTask.expirationTime > currentTime &&
(!hasTimeRemaining || shouldYieldToHost())
) {
// This currentTask hasn't expired, and we've reached the deadline.
break;
}
const callback = currentTask.callback;
if (callback !== null) {
currentTask.callback = null;
currentPriorityLevel = currentTask.priorityLevel;
const didUserCallbackTimeout = currentTask.expirationTime <= currentTime;
markTaskRun(currentTask, currentTime);
const continuationCallback = callback(didUserCallbackTimeout);
currentTime = getCurrentTime();
if (typeof continuationCallback === 'function') {
currentTask.callback = continuationCallback;
markTaskYield(currentTask, currentTime);
} else {
if (enableProfiling) {
markTaskCompleted(currentTask, currentTime);
currentTask.isQueued = false;
}
if (currentTask === peek(taskQueue)) {
pop(taskQueue);
}
}
advanceTimers(currentTime);
} else {
pop(taskQueue);
}
currentTask = peek(taskQueue);
}
// Return whether there's additional work
if (currentTask !== null) {
return true;
} else {
const firstTimer = peek(timerQueue);
if (firstTimer !== null) {
requestHostTimeout(handleTimeout, firstTimer.startTime - currentTime);
}
return false;
}
}
复制代码
最终都会经过 performUnitOfWork
操做。
这个方法只不过异步的方法是能够打断的,咱们每次调用都要查看是否超时。
function performUnitOfWork(unitOfWork: Fiber): void {
// The current, flushed, state of this fiber is the alternate. Ideally
// nothing should rely on this, but relying on it here means that we don't
// need an additional field on the work in progress.
const current = unitOfWork.alternate;
setCurrentDebugFiberInDEV(unitOfWork);
let next;
if (enableProfilerTimer && (unitOfWork.mode & ProfileMode) !== NoMode) {
startProfilerTimer(unitOfWork);
next = beginWork(current, unitOfWork, renderExpirationTime);
stopProfilerTimerIfRunningAndRecordDelta(unitOfWork, true);
} else {
next = beginWork(current, unitOfWork, renderExpirationTime);
}
resetCurrentDebugFiberInDEV();
unitOfWork.memoizedProps = unitOfWork.pendingProps;
if (next === null) {
// If this doesn't spawn new work, complete the current work.
completeUnitOfWork(unitOfWork);
} else {
workInProgress = next;
}
ReactCurrentOwner.current = null;
}
复制代码
上面的 startProfilerTimer
和 stopProfilerTimerIfRunningAndRecordDelta
其实就是记录 fiber 的工做时长。
function startProfilerTimer(fiber: Fiber): void {
if (!enableProfilerTimer) {
return;
}
profilerStartTime = now();
if (((fiber.actualStartTime: any): number) < 0) {
fiber.actualStartTime = now();
}
}
function stopProfilerTimerIfRunningAndRecordDelta( fiber: Fiber, overrideBaseTime: boolean, ): void {
if (!enableProfilerTimer) {
return;
}
if (profilerStartTime >= 0) {
const elapsedTime = now() - profilerStartTime;
fiber.actualDuration += elapsedTime;
if (overrideBaseTime) {
fiber.selfBaseDuration = elapsedTime;
}
profilerStartTime = -1;
}
}
复制代码
最后,就到了 beginWork
流程了 - -。里面有什么呢? workInProgress
还有一大堆的 switch case
。
想看 beginWork
源码的能够自行尝试 beginWork相关源码文件
最后是总结部分,该不应写这个想了好久,每一个读者在不一样时间不一样心境下看源码的感悟应该是不同的(固然本身回顾的时候也是读者)。每次看应该都有每一个时期的总结。
可是若是不写总结,这篇解析又感受枯燥无味,且没有结果。因此简单略过一下(确定是原创啦,别的地方没有的)
expirationTime
获得过时时间settimeout
+ postMessage
实现的clearTimeout
使用链表结构只是一个结果,而不是目的,React 开发者一开始的目的是冲着模拟调用栈去的
调用栈最常常被用于存放子程序的返回地址。在调用任何子程序时,主程序都必须暂存子程序运行完毕后应该返回到的地址。所以,若是被调用的子程序还要调用其余的子程序,其自身的返回地址就必须存入调用栈,在其自身运行完毕后再行取回。除了返回地址,还会保存本地变量、函数参数、环境传递。
所以 Fiber 对象被设计成一个链表结构,经过如下主要属性组成一个链表
type
类型return
存储当前节点的父节点child
存储第一个子节点sibling
存储右边第一个的兄弟节点alternate
旧树的同等节点咱们在遍历 dom 树 diff 的时候,即便中断了,咱们只须要记住中断时候的那么一个节点,就能够在下个时间片恢复继续遍历并 diff。这就是 fiber 数据结构选用链表的一大好处。
浏览器个周期执行的事件
1. 宏任务
2. 微任务
4. requestAnimationFrame
5. IntersectionObserver
6. 更新界面
7. requestIdleCallback
8. 下一帧
复制代码
根据官方描述:
window.requestIdleCallback()
方法将在浏览器的空闲时段内调用的函数排队。这使开发者可以在主事件循环上执行后台和低优先级工做,而不会影响延迟关键事件,如动画和输入响应。函数通常会按先进先调用的顺序执行,然而,若是回调函数指定了执行超时时间timeout
,则有可能为了在超时前执行函数而打乱执行顺序。 你能够在空闲回调函数中调用requestIdleCallback()
,以便在下一次经过事件循环以前调度另外一个回调。
看似完美契合时间切片的思想,因此起初 React 的时间分片渲染就想要用到这个 API,不过目前浏览器支持的不给力,并且 requestIdleCallback
有点过于严格,而且执行频率不足以实现流畅的UI呈现。
并且咱们但愿经过Fiber 架构,让 reconcilation
过程变成可被中断。'适时'地让出 CPU 执行权。所以React团队不得不实现本身的版本。
实际上 Fiber 的思想和协程的概念是契合的。举个栗子:
普通函数: (没法被中断和恢复)
const tasks = []
function run() {
let task
while (task = tasks.shift()) {
execute(task)
}
}
复制代码
若是使用 Generator
语法:
const tasks = []
function * run() {
let task
while (task = tasks.shift()) {
// 判断是否有高优先级事件须要处理, 有的话让出控制权
if (hasHighPriorityEvent()) {
yield
}
// 处理完高优先级事件后,恢复函数调用栈,继续执行...
execute(task)
}
}
复制代码
可是 React 尝试过用 Generator 实现,后来发现很麻烦,就放弃了。
主要是2个缘由:
Generator
必须将每一个函数都包装在 Generator 堆栈中。这不只增长了不少语法开销,并且还增长了现有实现中的运行时开销。虽然有胜于无,可是性能问题仍然存在。是否能够经过 Web Worker
来建立多线程环境来实现时间切片呢?
React 团队也曾经考虑过,尝试提出共享的不可变持久数据结构,尝试了自定义 VM 调整等,可是 JavaScript
该语言不适用于此。
由于可变的共享运行时(例如原型),生态系统尚未作好准备,由于你必须跨工做人员重复代码加载和模块初始化。若是垃圾回收器必须是线程安全的,则它们的效率不如当前高效,而且VM实现者彷佛不肯意承担持久数据结构的实现成本。共享的可变类型数组彷佛正在发展,可是在当今的生态系统中,要求全部数据经过此层彷佛是不可行的。代码库的不一样部分之间的人为边界也没法很好地工做,而且会带来没必要要的摩擦。即便那样,你仍然有不少JS代码(例如实用程序库)必须在工做人员之间复制。这会致使启动时间和内存开销变慢。所以,是的,在咱们能够定位诸如Web Assembly之类的东西以前,线程多是不可能的。
你没法安全地停止后台线程。停止和重启线程并非很便宜。在许多语言中,它也不安全,由于你可能处于一些懒惰的初始化工做之中。即便它被有效地中断了,你也必须继续在它上面花费CPU周期。
另外一个限制是,因为没法当即停止线程,所以没法肯定两个线程是否同时处理同一组件。这致使了一些限制,例如没法支持有状态的类实例(如React.Component)。线程不能只记住你在一个线程中完成的部分工做并在另外一个线程中重复使用。
ps: 本菜不会用 React,第一次读 React 源码,对源码有误读请指正
全栈
或 Vue
有好礼相送哦