背景
老的react架構在渲染時會有一些性能問題,從setstate到render,程序一直在跑,一直到render完成。才能繼續下一步操作。如果組件比較多,或者有復雜的計算邏輯,這之間的消耗的時間是比較多的。
假設更新一個組件需要1ms,如果有200個組件要更新,那就需要200ms,這200ms之間是不能響應的。如果這時候用戶在input框輸入什么東西,表現出來的就是明顯的卡頓。
React這樣的調度策略對動畫的支持也不好。如果React更新一次狀態,占用瀏覽器主線程的時間超過16.6ms,就會被人眼發覺前后兩幀不連續,呈現出動畫卡頓。
Fiber
react團隊經過兩年的工作,重寫了react中核心算法reconciliation。並在v16版本中發布了這個新的特性。為了區別之前和之后的reconciler,通常將之前的reconciler稱為stack reconciler,重寫后的稱為fiber reconciler,簡稱為Fiber。
區別
最大的變化就是支持了任務幀,把各個任務都增加了優先級,同步和異步。比如用戶輸入input是優先級比較高的,它可以打斷低優先級的任務。
比如再處理dom diff的時候耗時嚴重,fiber任務處理大概會有50ms的幀時長,超過這個時間就會先去看看有沒高優任務去做。然后回來做低優先級任務。
- 優先級高的任務可以中斷低優先級的任務。
- 還增加了異步任務,調用requestIdleCallback api,瀏覽器空閑的時候執行。(不過用戶操作默認是同步的,暫時還沒開放這個特性)
- dom diff樹變成了鏈表,一個dom對應兩個fiber(一個鏈表),對應兩個隊列,這都是為找到被中斷的任務,重新執行而設計的。
渲染流程
scheduleWork - requestWork - 同步/異步 - performSyncWork- performWork - performWorkOnRoot -
renderRoot/completeRoot - workLoop-performUnitOfWork-beginWork/completeUnitOfWork -updateClassComponent-reconcileChildrenAtExpirationTime- reconcileChildFibers-reconcileChildrenArray
源碼基於react v16.3.0 (8e3d94ff)
setstate
Component.prototype.setState = function(partialState, callback) {
this.updater.enqueueSetState(this, partialState, callback, 'setState');
};
enqueueSetState
主要是把任務插入fiber的update queue,然后調度任務
enqueueSetState(instance, partialState, callback) {
const fiber = ReactInstanceMap.get(instance);
callback = callback === undefined ? null : callback;
const expirationTime = computeExpirationForFiber(fiber);
const update = {
expirationTime,
partialState,
callback,
isReplace: false,
isForced: false,
capturedValue: null,
next: null,
};
insertUpdateIntoFiber(fiber, update);
scheduleWork(fiber, expirationTime);
},
insertUpdateIntoFiber
插入fiber兩棵樹的update queue
每個react 結點都有2個fiber鏈表,一個叫current fiber,一個叫alternate fiber,而每個鏈表又對應兩個updateQueue。
而currentFiber.alternate = alternateFiber; alternateFiber.alternate = currentFiber。通過alternate屬性連接起來。初始化的時候,alternate fiber是current fiber 的clone。
處理diff的時候,操作的是alternateFiber,處理完diff,讓currentFiber = alternateFiber;這樣一個處理就完成了。
scheduleWork
scheduleWork會更新每個節點的優先級,然后循環到root,以后的操作都從root開始遍歷。
- expirationTime 優先級 expirationTime 不為 1 的時候,則其值越低,優先級越高。
{
NoWork: 0, // No work is pending.
SynchronousPriority: 1, // For controlled text inputs. Synchronous side-effects.
AnimationPriority: 2, // Needs to complete before the next frame.
HighPriority: 3, // Interaction that needs to complete pretty soon to feel responsive.
LowPriority: 4, // Data fetching, or result from updating stores.
OffscreenPriority: 5, // Won't be visible but do the work in case it becomes visible.
};
function scheduleWork(fiber: Fiber, expirationTime: ExpirationTime) {
return scheduleWorkImpl(fiber, expirationTime, false);
}
function scheduleWorkImpl(
fiber: Fiber,
expirationTime: ExpirationTime,
isErrorRecovery: boolean,
) {
recordScheduleUpdate(); // 記錄更新,實際啥也沒干
let node = fiber;
while (node !== null) {
// Walk the parent path to the root and update each node's
// expiration time.
// 更新每個node的優先級
if (
node.expirationTime === NoWork ||
node.expirationTime > expirationTime
) {
node.expirationTime = expirationTime;
}
if (node.alternate !== null) {
if (
node.alternate.expirationTime === NoWork ||
node.alternate.expirationTime > expirationTime
) {
node.alternate.expirationTime = expirationTime;
}
}
if (node.return === null) {
if (node.tag === HostRoot) {
const root: FiberRoot = (node.stateNode: any);
if (
!isWorking &&
nextRenderExpirationTime !== NoWork &&
expirationTime < nextRenderExpirationTime
) {
// This is an interruption. (Used for performance tracking.)
interruptedBy = fiber;
resetStack();
}
if (
// If we're in the render phase, we don't need to schedule this root
// for an update, because we'll do it before we exit...
!isWorking ||
isCommitting ||
// ...unless this is a different root than the one we're rendering.
nextRoot !== root
) {
// Add this root to the root schedule.
requestWork(root, expirationTime);
}
} else {
}
return;
}
}
node = node.return;
}
}
requestWork
同步執行performSyncWork,異步執行scheduleCallbackWithExpiration,
scheduleCallbackWithExpiration會調瀏覽器的requestidlecallback,在瀏覽器空閑的時候進行處理。
react還對這個api做了polyfill
function requestWork(root: FiberRoot, expirationTime: ExpirationTime) {
if (isRendering) {
return;
}
if (isBatchingUpdates) { // 這里是BatchingUpdates的處理。
// Flush work at the end of the batch.
if (isUnbatchingUpdates) {
// ...unless we're inside unbatchedUpdates, in which case we should
// flush it now.
nextFlushedRoot = root;
nextFlushedExpirationTime = Sync;
performWorkOnRoot(root, Sync, false);
}
return;
}
if (expirationTime === Sync) {
performSyncWork();
} else {
scheduleCallbackWithExpiration(expirationTime);
}
}
performSyncWork 主要的任務調度
這里會找到高優任務先執行。
同步任務會直接調用performWorkOnRoot進行下一步,
異步任務也會調performWorkOnRoot,但處理不太一樣
如果有上次遺留的任務,留到空閑時運行
function performSyncWork() {
performWork(Sync, false, null);
}
function performWork(
minExpirationTime: ExpirationTime,
isAsync: boolean,
dl: Deadline | null,
) {
deadline = dl;
findHighestPriorityRoot();
if (isAsync) {
while (
nextFlushedRoot !== null &&
nextFlushedExpirationTime !== NoWork &&
(minExpirationTime === NoWork ||
minExpirationTime >= nextFlushedExpirationTime) &&
(!deadlineDidExpire ||
recalculateCurrentTime() >= nextFlushedExpirationTime)
) {
performWorkOnRoot(
nextFlushedRoot,
nextFlushedExpirationTime,
!deadlineDidExpire,
);
findHighestPriorityRoot();
}
} else {
while (
nextFlushedRoot !== null &&
nextFlushedExpirationTime !== NoWork &&
(minExpirationTime === NoWork ||
minExpirationTime >= nextFlushedExpirationTime)
) {
performWorkOnRoot(nextFlushedRoot, nextFlushedExpirationTime, false);
findHighestPriorityRoot();
}
}
if (deadline !== null) {
callbackExpirationTime = NoWork;
callbackID = -1;
}
// If there's work left over, schedule a new callback.
if (nextFlushedExpirationTime !== NoWork) {
scheduleCallbackWithExpiration(nextFlushedExpirationTime);
}
// Clean-up.
deadline = null;
deadlineDidExpire = false;
finishRendering();
}
performWorkOnRoot (異步任務和同步任務的異同)
如果有上次遺留,直接調用completeRoot進到渲染階段。如果沒有就調renderRoot開始reconcilation階段。
異步任務主要是渲染的時候判斷一下時間,如果沒時間了,先把finishedWork賦給全局,下次循環處理。
function performWorkOnRoot(
root: FiberRoot,
expirationTime: ExpirationTime,
isAsync: boolean,
) {
isRendering = true;
// Check if this is async work or sync/expired work.
if (!isAsync) {
// Flush sync work.
let finishedWork = root.finishedWork;
if (finishedWork !== null) {
// This root is already complete. We can commit it.
completeRoot(root, finishedWork, expirationTime);
} else {
root.finishedWork = null;
finishedWork = renderRoot(root, expirationTime, false);
if (finishedWork !== null) {
// We've completed the root. Commit it.
completeRoot(root, finishedWork, expirationTime);
}
}
} else {
// Flush async work.
let finishedWork = root.finishedWork;
if (finishedWork !== null) {
// This root is already complete. We can commit it.
completeRoot(root, finishedWork, expirationTime);
} else {
root.finishedWork = null;
finishedWork = renderRoot(root, expirationTime, true);
if (finishedWork !== null) {
// We've completed the root. Check the deadline one more time
// before committing.
if (!shouldYield()) {
// Still time left. Commit the root.
completeRoot(root, finishedWork, expirationTime);
} else {
// There's no time left. Mark this root as complete. We'll come
// back and commit it later.
root.finishedWork = finishedWork;
}
}
}
}
isRendering = false;
}
renderRoot
如果是第一次進入,會創建一個nextUnitOfWork。
nextUnitOfWork是每個工作的粒度。
然后調用workLoop
function renderRoot(
root: FiberRoot,
expirationTime: ExpirationTime,
isAsync: boolean,
): Fiber | null {
isWorking = true;
// Check if we're starting from a fresh stack, or if we're resuming from
// previously yielded work.
if (
expirationTime !== nextRenderExpirationTime ||
root !== nextRoot ||
nextUnitOfWork === null
) {
// Reset the stack and start working from the root.
resetStack();
nextRoot = root;
nextRenderExpirationTime = expirationTime;
nextUnitOfWork = createWorkInProgress(
nextRoot.current,
null,
nextRenderExpirationTime,
);
root.pendingCommitExpirationTime = NoWork;
}
let didFatal = false;
startWorkLoopTimer(nextUnitOfWork);
do {
try {
workLoop(isAsync);
} catch (thrownValue) {
// ...
}
break;
} while (true);
// We're done performing work. Time to clean up.
// ...
}
workLoop
異步任務在處理的時候會調用shouldYield,shouldYield會判斷是不是已經超時了,超時暫時先不做。
function workLoop(isAsync) {
if (!isAsync) {
// Flush all expired work.
while (nextUnitOfWork !== null) {
nextUnitOfWork = performUnitOfWork(nextUnitOfWork);
}
} else {
// Flush asynchronous work until the deadline runs out of time.
while (nextUnitOfWork !== null && !shouldYield()) {
nextUnitOfWork = performUnitOfWork(nextUnitOfWork);
}
}
}
function shouldYield() {
if (deadline === null) {
return false;
}
if (deadline.timeRemaining() > timeHeuristicForUnitOfWork) {
// Disregard deadline.didTimeout. Only expired work should be flushed
// during a timeout. This path is only hit for non-expired work.
return false;
}
deadlineDidExpire = true;
return true;
}
performUnitOfWork (reconcilation階段)
reconcilation又分兩步
1是beginWork,beginWork會開始處理組件,針對不同組件不同處理。包括dom diff
2 是completeUnitOfWork,completeUnitOfWork會對begin work產生的effect list進行一些處理。
function performUnitOfWork(workInProgress: Fiber): Fiber | null {
const current = workInProgress.alternate;
startWorkTimer(workInProgress);
let next = beginWork(current, workInProgress, nextRenderExpirationTime);
if (next === null) {
next = completeUnitOfWork(workInProgress);
}
ReactCurrentOwner.current = null;
return next;
}
beginWork
主要是對react 組件進行一些操作。和調用一些生命周期,
我們主要關注classComponent,就是react的組件
HostConponent在瀏覽器下就是dom
function beginWork(
current: Fiber | null,
workInProgress: Fiber,
renderExpirationTime: ExpirationTime,
): Fiber | null {
if (
workInProgress.expirationTime === NoWork ||
workInProgress.expirationTime > renderExpirationTime
) {
return bailoutOnLowPriority(current, workInProgress);
}
switch (workInProgress.tag) {
case FunctionalComponent:
return updateFunctionalComponent(current, workInProgress);
case ClassComponent:
return updateClassComponent(
current,
workInProgress,
renderExpirationTime,
);
case HostRoot:
return updateHostRoot(current, workInProgress, renderExpirationTime);
case HostComponent:
return updateHostComponent(
current,
workInProgress,
renderExpirationTime,
);
case HostText:
return updateHostText(current, workInProgress);
case ForwardRef:
return updateForwardRef(current, workInProgress);
case Fragment:
return updateFragment(current, workInProgress);
case Mode:
return updateMode(current, workInProgress);
case ContextProvider:
return updateContextProvider(
current,
workInProgress,
renderExpirationTime,
);
case ContextConsumer:
return updateContextConsumer(
current,
workInProgress,
renderExpirationTime,
);
default:
invariant(
false,
'Unknown unit of work tag. This error is likely caused by a bug in ' +
'React. Please file an issue.',
);
}
}
updateClassComponent
mount組件,構建組件實例,調用生命周期比如willMount,初始化組件的的updateQueue。
- updateClassInstance中,如果props不一致,會調willReceiveProps方法,然后checkShouldCompoentUpdate,也就是
shouldCompoentUpdate。 - finishClassComponent中,會判斷之前的shouldUpdate,如果是true就要調用組件的render,產出children,然后對children進行dom diff。
function updateClassComponent(
current: Fiber | null,
workInProgress: Fiber,
renderExpirationTime: ExpirationTime,
) {
// Push context providers early to prevent context stack mismatches.
// During mounting we don't know the child context yet as the instance doesn't exist.
// We will invalidate the child context in finishClassComponent() right after rendering.
const hasContext = pushLegacyContextProvider(workInProgress);
let shouldUpdate;
if (current === null) {
if (workInProgress.stateNode === null) {
// In the initial pass we might need to construct the instance.
constructClassInstance(workInProgress, workInProgress.pendingProps);
mountClassInstance(workInProgress, renderExpirationTime);
shouldUpdate = true;
} else {
// In a resume, we'll already have an instance we can reuse.
shouldUpdate = resumeMountClassInstance(
workInProgress,
renderExpirationTime,
);
}
} else {
shouldUpdate = updateClassInstance(
current,
workInProgress,
renderExpirationTime,
);
}
let didCaptureError = false;
const updateQueue = workInProgress.updateQueue;
if (updateQueue !== null && updateQueue.capturedValues !== null) {
shouldUpdate = true;
didCaptureError = true;
}
return finishClassComponent(
current,
workInProgress,
shouldUpdate,
hasContext,
didCaptureError,
renderExpirationTime,
);
}
reconcileChildFibers (virtul dom diff)
finishClassComponent會調用reconcileChildFibers進行dom diff。
function reconcileChildFibers(
returnFiber: Fiber,
currentFirstChild: Fiber | null,
newChild: any,
expirationTime: ExpirationTime,
): Fiber | null {
if (
typeof newChild === 'object' &&
newChild !== null &&
newChild.type === REACT_FRAGMENT_TYPE &&
newChild.key === null
) {
newChild = newChild.props.children;
}
// Handle object types
const isObject = typeof newChild === 'object' && newChild !== null;
if (isObject) {
switch (newChild.$$typeof) {
case REACT_ELEMENT_TYPE:
return placeSingleChild(
reconcileSingleElement(
returnFiber,
currentFirstChild,
newChild,
expirationTime,
),
);
case REACT_PORTAL_TYPE:
return placeSingleChild(
reconcileSinglePortal(
returnFiber,
currentFirstChild,
newChild,
expirationTime,
),
);
}
}
if (typeof newChild === 'string' || typeof newChild === 'number') {
return placeSingleChild(
reconcileSingleTextNode(
returnFiber,
currentFirstChild,
'' + newChild,
expirationTime,
),
);
}
if (isArray(newChild)) {
return reconcileChildrenArray(
returnFiber,
currentFirstChild,
newChild,
expirationTime,
);
}
if (getIteratorFn(newChild)) {
return reconcileChildrenIterator(
returnFiber,
currentFirstChild,
newChild,
expirationTime,
);
}
}
reconcileChildrenArray
大部分情況是reconcileChildrenArray,就那這個來說。
function reconcileChildrenArray(
returnFiber: Fiber,
currentFirstChild: Fiber | null,
newChildren: Array<*>,
expirationTime: ExpirationTime,
): Fiber | null {
let resultingFirstChild: Fiber | null = null;
let previousNewFiber: Fiber | null = null;
let oldFiber = currentFirstChild;
let lastPlacedIndex = 0;
let newIdx = 0;
let nextOldFiber = null;
for (; oldFiber !== null && newIdx < newChildren.length; newIdx++) {
// 沒有采用兩端同時對比,受限於Fiber列表的單向結構
if (oldFiber.index > newIdx) {
nextOldFiber = oldFiber;
oldFiber = null;
} else {
nextOldFiber = oldFiber.sibling;
}
const newFiber = updateSlot( // 生成新的fiber
returnFiber,
oldFiber,
newChildren[newIdx],
expirationTime,
);
//如果在遍歷中發現key值不相等的情況,則直接跳出第一輪遍歷
if (newFiber === null) {
if (oldFiber === null) {
oldFiber = nextOldFiber;
}
break;
}
if (shouldTrackSideEffects) {
if (oldFiber && newFiber.alternate === null) {
// 我們找到了匹配的節點,但我們並不保留當前的Fiber,所以我們需要刪除當前的子節點
// We matched the slot, but we didn't reuse the existing fiber, so we
// need to delete the existing child.
deleteChild(returnFiber, oldFiber);
}
}
lastPlacedIndex = placeChild(newFiber, lastPlacedIndex, newIdx);
// 記錄上一個更新的子節點
if (previousNewFiber === null) {
resultingFirstChild = newFiber;
} else {
previousNewFiber.sibling = newFiber;
}
previousNewFiber = newFiber;
oldFiber = nextOldFiber;
}
if (newIdx === newChildren.length) {
// 我們已經遍歷完了所有的新節點,直接刪除剩余舊節點
// We've reached the end of the new children. We can delete the rest.
deleteRemainingChildren(returnFiber, oldFiber);
return resultingFirstChild;
}
if (oldFiber === null) {
// 如果舊節點先遍歷完,則按順序插入剩余的新節點
// If we don't have any more existing children we can choose a fast path
// since the rest will all be insertions.
for (; newIdx < newChildren.length; newIdx++) {
const newFiber = createChild(
returnFiber,
newChildren[newIdx],
expirationTime,
);
if (!newFiber) {
continue;
}
lastPlacedIndex = placeChild(newFiber, lastPlacedIndex, newIdx);
if (previousNewFiber === null) {
// TODO: Move out of the loop. This only happens for the first run.
resultingFirstChild = newFiber;
} else {
previousNewFiber.sibling = newFiber;
}
previousNewFiber = newFiber;
}
return resultingFirstChild;
}
// 把子節點都設置快速查找的map映射集
const existingChildren = mapRemainingChildren(returnFiber, oldFiber);
// 使用map查找需要保存或刪除的節點
for (; newIdx < newChildren.length; newIdx++) {
const newFiber = updateFromMap(
existingChildren,
returnFiber,
newIdx,
newChildren[newIdx],
expirationTime,
);
if (newFiber) {
if (shouldTrackSideEffects) {
if (newFiber.alternate !== null) {
// 新的Fiber也是一個工作線程,但是如果已有當前的實例,那我們就可以復用這個Fiber,
// 我們要從Map中刪除這個新的,避免准備復用的Fiber被刪除
existingChildren.delete(
newFiber.key === null ? newIdx : newFiber.key,
);
}
}
lastPlacedIndex = placeChild(newFiber, lastPlacedIndex, newIdx);
if (previousNewFiber === null) {
resultingFirstChild = newFiber;
} else {
previousNewFiber.sibling = newFiber;
}
previousNewFiber = newFiber;
}
}
if (shouldTrackSideEffects) {
// Any existing children that weren't consumed above were deleted. We need
// to add them to the deletion list.
// 到此所有剩余的Map的節點都將被刪除,加入刪除隊列
existingChildren.forEach(child => deleteChild(returnFiber, child));
}
// 最終返回Fiber子節點列表的第一個節點
return resultingFirstChild;
}
可以看到其實刪除節點並不是直接刪除而是打個Deletion的tag。生成effect list
function deleteChild(returnFiber: Fiber, childToDelete: Fiber): void {
const last = returnFiber.lastEffect;
if (last !== null) {
last.nextEffect = childToDelete;
returnFiber.lastEffect = childToDelete;
} else {
returnFiber.firstEffect = returnFiber.lastEffect = childToDelete;
}
childToDelete.nextEffect = null;
childToDelete.effectTag = Deletion;
}
completeUnitOfWork
在dom diff之后會有一個收尾工作大概就是effect的各種處理,就是workLoop之后的completeUnitOfWork函數。
同步effect list到 current 的host root 樹。
調用completeWork
function completeUnitOfWork(workInProgress: Fiber): Fiber | null {
while (true) {
const current = workInProgress.alternate;
const returnFiber = workInProgress.return;
const siblingFiber = workInProgress.sibling;
if ((workInProgress.effectTag & Incomplete) === NoEffect) {
// This fiber completed.
let next = completeWork(
current,
workInProgress,
nextRenderExpirationTime,
);
stopWorkTimer(workInProgress);
resetExpirationTime(workInProgress, nextRenderExpirationTime);
if (next !== null) {
stopWorkTimer(workInProgress);
if (__DEV__ && ReactFiberInstrumentation.debugTool) {
ReactFiberInstrumentation.debugTool.onCompleteWork(workInProgress);
}
// If completing this work spawned new work, do that next. We'll come
// back here again.
return next;
}
// 將當前fiber子樹上的effect list 插入到當前hostRoot 樹的effectlist中
if (
returnFiber !== null &&
// Do not append effects to parents if a sibling failed to complete
(returnFiber.effectTag & Incomplete) === NoEffect
) {
// Append all the effects of the subtree and this fiber onto the effect
// list of the parent. The completion order of the children affects the
// side-effect order.
if (returnFiber.firstEffect === null) {
returnFiber.firstEffect = workInProgress.firstEffect;
}
if (workInProgress.lastEffect !== null) {
if (returnFiber.lastEffect !== null) {
returnFiber.lastEffect.nextEffect = workInProgress.firstEffect;
}
returnFiber.lastEffect = workInProgress.lastEffect;
}
// If this fiber had side-effects, we append it AFTER the children's
// side-effects. We can perform certain side-effects earlier if
// needed, by doing multiple passes over the effect list. We don't want
// to schedule our own side-effect on our own list because if end up
// reusing children we'll schedule this effect onto itself since we're
// at the end.
const effectTag = workInProgress.effectTag;
// Skip both NoWork and PerformedWork tags when creating the effect list.
// PerformedWork effect is read by React DevTools but shouldn't be committed.
if (effectTag > PerformedWork) {
if (returnFiber.lastEffect !== null) {
returnFiber.lastEffect.nextEffect = workInProgress;
} else {
returnFiber.firstEffect = workInProgress;
}
returnFiber.lastEffect = workInProgress;
}
}
if (siblingFiber !== null) {
// If there is more work to do in this returnFiber, do that next.
return siblingFiber;
} else if (returnFiber !== null) {
// If there's no more work in this returnFiber. Complete the returnFiber.
workInProgress = returnFiber;
continue;
} else {
// We've reached the root.
isRootReadyForCommit = true;
return null;
}
} else {
// This fiber did not complete because something threw. Pop values off
// the stack without entering the complete phase. If this is a boundary,
// capture values if possible.
const next = unwindWork(workInProgress);
// Because this fiber did not complete, don't reset its expiration time.
if (workInProgress.effectTag & DidCapture) {
// Restarting an error boundary
stopFailedWorkTimer(workInProgress);
} else {
stopWorkTimer(workInProgress);
}
if (next !== null) {
stopWorkTimer(workInProgress);
if (__DEV__ && ReactFiberInstrumentation.debugTool) {
ReactFiberInstrumentation.debugTool.onCompleteWork(workInProgress);
}
// If completing this work spawned new work, do that next. We'll come
// back here again.
// Since we're restarting, remove anything that is not a host effect
// from the effect tag.
next.effectTag &= HostEffectMask;
return next;
}
if (returnFiber !== null) {
// Mark the parent fiber as incomplete and clear its effect list.
returnFiber.firstEffect = returnFiber.lastEffect = null;
returnFiber.effectTag |= Incomplete;
}
if (siblingFiber !== null) {
// If there is more work to do in this returnFiber, do that next.
return siblingFiber;
} else if (returnFiber !== null) {
// If there's no more work in this returnFiber. Complete the returnFiber.
workInProgress = returnFiber;
continue;
} else {
return null;
}
}
}
return null;
}
completeWork
比較長,不貼代碼了。主要做的事情就是根據不同的component類型進行不同的處理。
重點是對HostComponent的props進行diff,並標記更新。
如果是react首次渲染,調用createInstance創建一個HostComponent。
如果已經存在HostComponent,檢查節點是否需要更新,調用prepareUpdate,進行diff dom屬性。
到此reconciliation階段結束,主要負責產出effect list。
可以說reconcile的過程相當於是一個純函數,輸入是fiber節點,輸出一個effect list。
因為純函數的可預測性,讓我們可以隨時中斷reconciliation階段的執行,而不用擔心side-effects給讓組件狀態和實際UI產生不一致
渲染階段 completeRoot/commitRoot
function commitRoot(finishedWork: Fiber): ExpirationTime {
isWorking = true;
isCommitting = true;
startCommitTimer();
const root: FiberRoot = finishedWork.stateNode;
const committedExpirationTime = root.pendingCommitExpirationTime;
root.pendingCommitExpirationTime = NoWork;
const currentTime = recalculateCurrentTime();
// Reset this to null before calling lifecycles
ReactCurrentOwner.current = null;
let firstEffect;
if (finishedWork.effectTag > PerformedWork) {
// fiber的effect list只包括它子樹中的effects,將節點的effect加到effect list鏈表中
if (finishedWork.lastEffect !== null) {
finishedWork.lastEffect.nextEffect = finishedWork;
firstEffect = finishedWork.firstEffect;
} else {
firstEffect = finishedWork;
}
} else {
// There is no effect on the root.
firstEffect = finishedWork.firstEffect;
}
// 做一些dom事件相關設置
prepareForCommit(root.containerInfo);
// Commit all the side-effects within a tree. We'll do this in two passes.
// The first pass performs all the host insertions, updates, deletions and
// ref unmounts.
nextEffect = firstEffect;
startCommitHostEffectsTimer();
while (nextEffect !== null) {
let didError = false;
let error;
try {
// 遍歷fiber樹的effect list,調用相關的生命周期,比如willUnmount。操作dom,完成渲染。
commitAllHostEffects();
} catch (e) {
didError = true;
error = e;
}
}
stopCommitHostEffectsTimer();
resetAfterCommit(root.containerInfo);
root.current = finishedWork;
nextEffect = firstEffect;
startCommitLifeCyclesTimer();
while (nextEffect !== null) {
let didError = false;
let error;
try {
// 再遍歷effect list,如果effect發生在classComponent上,加調didMount和didUpdate方法。
// 如果發生在hostComponents上,會調用commitMount方法,主要是為了在render一個節點渲染之后做一些操作。比如input的auto-focus。
commitAllLifeCycles(root, currentTime, committedExpirationTime);
} catch (e) {
didError = true;
error = e;
}
}
isCommitting = false;
isWorking = false;
stopCommitLifeCyclesTimer();
stopCommitTimer();
if (typeof onCommitRoot === 'function') {
onCommitRoot(finishedWork.stateNode);
}
const remainingTime = root.current.expirationTime;
if (remainingTime === NoWork) {
// If there's no remaining work, we can clear the set of already failed
// error boundaries.
legacyErrorBoundariesThatAlreadyFailed = null;
}
return remainingTime;
}