寫得很亂 還沒整理 可是一不當心發出來了 因此請不要噴我啊 大佬們!react
react v16版本重寫了react中核心算法——reconciliation。16以前稱爲stack reconciler,重寫後的稱爲fiber reconciler,簡稱爲Fiber。 fiber幫助咱們把react整個樹的應用拆分紅一個個fiber樹單元。這種拆分單元的形式可讓咱們給每個不一樣的任務提供優先級,在更新的過程當中能夠中斷,記錄更新到了哪個單元,過一會再從紀錄的單元開始繼續更新。 v16以前的版本則是一個同步的過程,必須等待計算結束才能夠進行dom更新。算法
看源碼的時候要理解她出於什麼目的這麼設計typescript
setState自己方法調用是同步,可是在調用setState的時候並不處於批量更新的狀況下(isbatchingUpdates爲true,沒有當即進行調度performSyncWork,只是建立updateQueue )就是異步的,setState沒有立馬更新, 若是處於立馬更新的狀況下,setState會有可能立馬更新。若是處於異步渲染的條件下,例如concurrentModel和aSyncModel,進入異步調度過程的時候也不會立馬更新。瀏覽器
根據ExpirationTime 插入單向鏈表裏app
不處於瀏覽器環境dom
模擬requestIdelCallback:經過requestAnimationFrame調用完callback以後立馬進入瀏覽器動畫更新的設定下,在任務隊列裏插入任務,在瀏覽器執行完以後就調用這個任務隊列,這個時間總共加起來是33ms異步
Fiber 的解決思路:把渲染更新過程拆分紅多個子任務,每次只作一小部分,作完看是否還有剩餘時間,若是有繼續下一個任務;若是沒有,掛起當前任務,將時間控制權交給主線程,等主線程不忙的時候在繼續執行。async
reactRoot建立的同時會建立一個fiberRoot,fiberRoot.current是一個fiber對象,fiberRoot上記錄着優先級最高和最低的任務。根據單向鏈表將每個fiber單元經過sibling和next鏈接起來。ide
function renderRoot( root: FiberRoot, expirationTime: ExpirationTime, isSync: boolean, ): SchedulerCallback | null {
invariant(
(executionContext & (RenderContext | CommitContext)) === NoContext,
'Should not already be working.',
);
if (root.firstPendingTime < expirationTime) {
// If there's no work left at this expiration time, exit immediately. This
// happens when multiple callbacks are scheduled for a single root, but an
// earlier callback flushes the work of a later one.
return null;
}
if (isSync && root.finishedExpirationTime === expirationTime) {
// There's already a pending commit at this expiration time.
// TODO: This is poorly factored. This case only exists for the
// batch.commit() API.
return commitRoot.bind(null, root);
}
flushPassiveEffects();
// If the root or expiration time have changed, throw out the existing stack
// and prepare a fresh one. Otherwise we'll continue where we left off.
if (root !== workInProgressRoot || expirationTime !== renderExpirationTime) {
prepareFreshStack(root, expirationTime);
startWorkOnPendingInteractions(root, expirationTime);
} else if (workInProgressRootExitStatus === RootSuspendedWithDelay) {
// We could've received an update at a lower priority while we yielded.
// We're suspended in a delayed state. Once we complete this render we're
// just going to try to recover at the last pending time anyway so we might
// as well start doing that eagerly.
// Ideally we should be able to do this even for retries but we don't yet
// know if we're going to process an update which wants to commit earlier,
// and this path happens very early so it would happen too often. Instead,
// for that case, we'll wait until we complete.
if (workInProgressRootHasPendingPing) {
// We have a ping at this expiration. Let's restart to see if we get unblocked.
prepareFreshStack(root, expirationTime);
} else {
const lastPendingTime = root.lastPendingTime;
if (lastPendingTime < expirationTime) {
// There's lower priority work. It might be unsuspended. Try rendering
// at that level immediately, while preserving the position in the queue.
return renderRoot.bind(null, root, lastPendingTime);
}
}
}
// If we have a work-in-progress fiber, it means there's still work to do
// in this root.
if (workInProgress !== null) {
const prevExecutionContext = executionContext;
executionContext |= RenderContext;
let prevDispatcher = ReactCurrentDispatcher.current;
if (prevDispatcher === null) {
// The React isomorphic package does not include a default dispatcher.
// Instead the first renderer will lazily attach one, in order to give
// nicer error messages.
prevDispatcher = ContextOnlyDispatcher;
}
ReactCurrentDispatcher.current = ContextOnlyDispatcher;
let prevInteractions: Set<Interaction> | null = null;
if (enableSchedulerTracing) {
prevInteractions = __interactionsRef.current;
__interactionsRef.current = root.memoizedInteractions;
}
startWorkLoopTimer(workInProgress);
// TODO: Fork renderRoot into renderRootSync and renderRootAsync
if (isSync) {
if (expirationTime !== Sync) {
// An async update expired. There may be other expired updates on
// this root. We should render all the expired work in a
// single batch.
const currentTime = requestCurrentTime();
if (currentTime < expirationTime) {
// Restart at the current time.
executionContext = prevExecutionContext;
resetContextDependencies();
ReactCurrentDispatcher.current = prevDispatcher;
if (enableSchedulerTracing) {
__interactionsRef.current = ((prevInteractions: any): Set<
Interaction,
>);
}
return renderRoot.bind(null, root, currentTime);
}
}
} else {
// Since we know we're in a React event, we can clear the current
// event time. The next update will compute a new event time.
currentEventTime = NoWork;
}
do {
try {
if (isSync) {
workLoopSync();
} else {
workLoop();
}
break;
} catch (thrownValue) {
// Reset module-level state that was set during the render phase.
resetContextDependencies();
resetHooks();
const sourceFiber = workInProgress;
if (sourceFiber === null || sourceFiber.return === null) {
// Expected to be working on a non-root fiber. This is a fatal error
// because there's no ancestor that can handle it; the root is
// supposed to capture all errors that weren't caught by an error
// boundary.
prepareFreshStack(root, expirationTime);
executionContext = prevExecutionContext;
throw thrownValue;
}
if (enableProfilerTimer && sourceFiber.mode & ProfileMode) {
// Record the time spent rendering before an error was thrown. This
// avoids inaccurate Profiler durations in the case of a
// suspended render.
stopProfilerTimerIfRunningAndRecordDelta(sourceFiber, true);
}
const returnFiber = sourceFiber.return;
throwException(
root,
returnFiber,
sourceFiber,
thrownValue,
renderExpirationTime,
);
workInProgress = completeUnitOfWork(sourceFiber);
}
} while (true);
executionContext = prevExecutionContext;
resetContextDependencies();
ReactCurrentDispatcher.current = prevDispatcher;
if (enableSchedulerTracing) {
__interactionsRef.current = ((prevInteractions: any): Set<Interaction>);
}
if (workInProgress !== null) {
// There's still work left over. Return a continuation.
stopInterruptedWorkLoopTimer();
return renderRoot.bind(null, root, expirationTime);
}
}
// We now have a consistent tree. The next step is either to commit it, or, if
// something suspended, wait to commit it after a timeout.
stopFinishedWorkLoopTimer();
root.finishedWork = root.current.alternate;
root.finishedExpirationTime = expirationTime;
const isLocked = resolveLocksOnRoot(root, expirationTime);
if (isLocked) {
// This root has a lock that prevents it from committing. Exit. If we begin
// work on the root again, without any intervening updates, it will finish
// without doing additional work.
return null;
}
// Set this to null to indicate there's no in-progress render.
workInProgressRoot = null;
switch (workInProgressRootExitStatus) {
case RootIncomplete: {
invariant(false, 'Should have a work-in-progress.');
}
// Flow knows about invariant, so it complains if I add a break statement,
// but eslint doesn't know about invariant, so it complains if I do.
// eslint-disable-next-line no-fallthrough
case RootErrored: {
// An error was thrown. First check if there is lower priority work
// scheduled on this root.
const lastPendingTime = root.lastPendingTime;
if (lastPendingTime < expirationTime) {
// There's lower priority work. Before raising the error, try rendering
// at the lower priority to see if it fixes it. Use a continuation to
// maintain the existing priority and position in the queue.
return renderRoot.bind(null, root, lastPendingTime);
}
if (!isSync) {
// If we're rendering asynchronously, it's possible the error was
// caused by tearing due to a mutation during an event. Try rendering
// one more time without yiedling to events.
prepareFreshStack(root, expirationTime);
scheduleSyncCallback(renderRoot.bind(null, root, expirationTime));
return null;
}
// If we're already rendering synchronously, commit the root in its
// errored state.
return commitRoot.bind(null, root);
}
case RootSuspended: {
flushSuspensePriorityWarningInDEV();
// We have an acceptable loading state. We need to figure out if we should
// immediately commit it or wait a bit.
// If we have processed new updates during this render, we may now have a
// new loading state ready. We want to ensure that we commit that as soon as
// possible.
const hasNotProcessedNewUpdates =
workInProgressRootLatestProcessedExpirationTime === Sync;
if (
hasNotProcessedNewUpdates &&
!isSync &&
// do not delay if we're inside an act() scope
!(
__DEV__ &&
flushSuspenseFallbacksInTests &&
IsThisRendererActing.current
)
) {
// If we have not processed any new updates during this pass, then this is
// either a retry of an existing fallback state or a hidden tree.
// Hidden trees shouldn't be batched with other work and after that's
// fixed it can only be a retry.
// We're going to throttle committing retries so that we don't show too
// many loading states too quickly.
let msUntilTimeout =
globalMostRecentFallbackTime + FALLBACK_THROTTLE_MS - now();
// Don't bother with a very short suspense time.
if (msUntilTimeout > 10) {
if (workInProgressRootHasPendingPing) {
// This render was pinged but we didn't get to restart earlier so try
// restarting now instead.
prepareFreshStack(root, expirationTime);
return renderRoot.bind(null, root, expirationTime);
}
const lastPendingTime = root.lastPendingTime;
if (lastPendingTime < expirationTime) {
// There's lower priority work. It might be unsuspended. Try rendering
// at that level.
return renderRoot.bind(null, root, lastPendingTime);
}
// The render is suspended, it hasn't timed out, and there's no lower
// priority work to do. Instead of committing the fallback
// immediately, wait for more data to arrive.
root.timeoutHandle = scheduleTimeout(
commitRoot.bind(null, root),
msUntilTimeout,
);
return null;
}
}
// The work expired. Commit immediately.
return commitRoot.bind(null, root);
}
case RootSuspendedWithDelay: {
flushSuspensePriorityWarningInDEV();
if (
!isSync &&
// do not delay if we're inside an act() scope
!(
__DEV__ &&
flushSuspenseFallbacksInTests &&
IsThisRendererActing.current
)
) {
// We're suspended in a state that should be avoided. We'll try to avoid committing
// it for as long as the timeouts let us.
if (workInProgressRootHasPendingPing) {
// This render was pinged but we didn't get to restart earlier so try
// restarting now instead.
prepareFreshStack(root, expirationTime);
return renderRoot.bind(null, root, expirationTime);
}
const lastPendingTime = root.lastPendingTime;
if (lastPendingTime < expirationTime) {
// There's lower priority work. It might be unsuspended. Try rendering
// at that level immediately.
return renderRoot.bind(null, root, lastPendingTime);
}
let msUntilTimeout;
if (workInProgressRootLatestSuspenseTimeout !== Sync) {
// We have processed a suspense config whose expiration time we can use as
// the timeout.
msUntilTimeout =
expirationTimeToMs(workInProgressRootLatestSuspenseTimeout) - now();
} else if (workInProgressRootLatestProcessedExpirationTime === Sync) {
// This should never normally happen because only new updates cause
// delayed states, so we should have processed something. However,
// this could also happen in an offscreen tree.
msUntilTimeout = 0;
} else {
// If we don't have a suspense config, we're going to use a heuristic to
// determine how long we can suspend.
const eventTimeMs: number = inferTimeFromExpirationTime(
workInProgressRootLatestProcessedExpirationTime,
);
const currentTimeMs = now();
const timeUntilExpirationMs =
expirationTimeToMs(expirationTime) - currentTimeMs;
let timeElapsed = currentTimeMs - eventTimeMs;
if (timeElapsed < 0) {
// We get this wrong some time since we estimate the time.
timeElapsed = 0;
}
msUntilTimeout = jnd(timeElapsed) - timeElapsed;
// Clamp the timeout to the expiration time.
// TODO: Once the event time is exact instead of inferred from expiration time
// we don't need this.
if (timeUntilExpirationMs < msUntilTimeout) {
msUntilTimeout = timeUntilExpirationMs;
}
}
// Don't bother with a very short suspense time.
if (msUntilTimeout > 10) {
// The render is suspended, it hasn't timed out, and there's no lower
// priority work to do. Instead of committing the fallback
// immediately, wait for more data to arrive.
root.timeoutHandle = scheduleTimeout(
commitRoot.bind(null, root),
msUntilTimeout,
);
return null;
}
}
// The work expired. Commit immediately.
return commitRoot.bind(null, root);
}
case RootCompleted: {
// The work completed. Ready to commit.
if (
!isSync &&
// do not delay if we're inside an act() scope
!(
__DEV__ &&
flushSuspenseFallbacksInTests &&
IsThisRendererActing.current
) &&
workInProgressRootLatestProcessedExpirationTime !== Sync &&
workInProgressRootCanSuspendUsingConfig !== null
) {
// If we have exceeded the minimum loading delay, which probably
// means we have shown a spinner already, we might have to suspend
// a bit longer to ensure that the spinner is shown for enough time.
const msUntilTimeout = computeMsUntilSuspenseLoadingDelay(
workInProgressRootLatestProcessedExpirationTime,
expirationTime,
workInProgressRootCanSuspendUsingConfig,
);
if (msUntilTimeout > 10) {
root.timeoutHandle = scheduleTimeout(
commitRoot.bind(null, root),
msUntilTimeout,
);
return null;
}
}
return commitRoot.bind(null, root);
}
default: {
invariant(false, 'Unknown root exit status.');
}
}
}
複製代碼
expirationTimeoop