前言:
在React源碼解析之renderRoot概覽中,提到了renderRoot()
會調用workLoop()/workLoopSync()進行循環單元的更新:javascript
function renderRoot(){
xxx
xxx
if (workInProgress !== null) {
//執行每一個節點的更新
if (isSync) {
workLoopSync();
} else {
//判斷是否須要繼續調用performUnitOfWork
workLoop();
}
}
xxx
xxx
}
複製代碼
本文就來說解workLoop
及其內部的方法邏輯php
注意:
本文涉及到 fiber 對象的部分屬性,請參考:React源碼解析之RootFiber java
1、workLoop
做用:
循環執行performUnitOfWork
並賦值給workInProgress
,直到workInProgress
值爲空,則停止循環react
源碼:git
//同步的 workLoop,說明是不能夠被中斷的
function workLoopSync() {
// Already timed out, so perform work without checking if we need to yield.
while (workInProgress !== null) {
workInProgress = performUnitOfWork(workInProgress);
}
}
//異步的 workLoop,說明是能夠被中斷的
//判斷是否須要繼續調用performUnitOfWork
function workLoop() {
// Perform work until Scheduler asks us to yield
/*nextUnitOfWork =》workInProgress*/
//未到達根節點時
//有workInProgress.child的時候,一直循環,直到全部節點更新完畢
while (workInProgress !== null && !shouldYield()) {
workInProgress = performUnitOfWork(workInProgress);
}
}
複製代碼
解析:
workInProgress 是一個 fiber 對象,它值不爲 null 意味着該 fiber 對象上仍然有更新要執行github
2、performUnitOfWork
做用:
調用beginWork
,從父至子,進行組件(節點)更新;
調用completeUnitOfWork
,從子至父,根據 effectTag,對節點進行一些處理web
源碼:app
//從上至下遍歷、操做節點,至底層後,再從下至上,根據effectTag 操做節點
//unitOfWork 即 workInProgress,是一個 fiber 對象
function performUnitOfWork(unitOfWork: Fiber): Fiber | null {
// 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.
//current <=> workInProgress
//獲取當前節點
const current = unitOfWork.alternate;
//在unitOfWork上作個標記,不看
startWorkTimer(unitOfWork);
//dev 環境,不看
setCurrentDebugFiberInDEV(unitOfWork);
let next;
if (enableProfilerTimer && (unitOfWork.mode & ProfileMode) !== NoMode) {
startProfilerTimer(unitOfWork);
//進行節點操做,並建立子節點
//current: workInProgress.alternate
//unitOfWork: workInProgress
//workInProgress.child
//判斷fiber有無更新,有更新則進行相應的組件更新,無更新則複製節點
next = beginWork(current, unitOfWork, renderExpirationTime);
stopProfilerTimerIfRunningAndRecordDelta(unitOfWork, true);
} else {
next = beginWork(current, unitOfWork, renderExpirationTime);
}
//不看
resetCurrentDebugFiberInDEV();
//將待更新的 props 替換成正在用的 props
unitOfWork.memoizedProps = unitOfWork.pendingProps;
//說明已經更新到了最底層的葉子節點,而且葉子節點的兄弟節點也已經遍歷完
if (next === null) {
// If this doesn't spawn new work, complete the current work.
//當從上到下遍歷完成後,completeUnitOfWork 會從下到上根據effact tag進行一些處理
next = completeUnitOfWork(unitOfWork);
}
ReactCurrentOwner.current = null;
return next;
}
複製代碼
解析:
(1) 獲取當前節點current
(2) 執行beginWork(current, unitOfWork, renderExpirationTime)
進行組件的更新,返回的值賦給next
(3) 將unitOfWork
上待更新的 props 替換成正在用的 props
(4) 若是next
爲 null,則執行completeUnitOfWork
,從下到上遍歷,並根據 effectTag 進行一些處理
(5) 最終返回next
(next 爲 fiber 對象)dom
看下beginWork()
。completeUnitOfWork()
會在後面文章中解析。異步
3、beginWork
注意:
switch…case… 那超長的兩段直接跳過
做用:
判斷fiber
有無更新,有更新則進行相應的組件更新,無更新則複製節點
源碼:
//判斷fiber有無更新,有更新則進行相應的組件更新,無更新則複製節點
//current: workInProgress.alternate
function beginWork(
current: Fiber | null,
//workInProgress建立的子節點也是workInProgress
workInProgress: Fiber,
//標記 該次渲染中,優先級最高的點
renderExpirationTime: ExpirationTime,
): Fiber | null {
//只有當調用 react.domRender的時候,rootFiber的expirationTime纔有值,rootFiber 纔會更新
//獲取 fiber 對象上更新的過時時間
const updateExpirationTime = workInProgress.expirationTime;
//判斷是否是第一次渲染
//若是不是第一次渲染
if (current !== null) {
//上一次渲染完成後的props,即 oldProps
const oldProps = current.memoizedProps;
//新的變更帶來的props,即newProps
const newProps = workInProgress.pendingProps;
if (
//先後 props 是否不相等
oldProps !== newProps ||
//是否有老版本的 context 使用,而且發生了變化
hasLegacyContextChanged() ||
// Force a re-render if the implementation changed due to hot reload:
//開發環境永遠是 false
(__DEV__ ? workInProgress.type !== current.type : false)
) {
// If props or context changed, mark the fiber as having performed work.
// This may be unset if the props are determined to be equal later (memo).
//判斷接收到了更新 update
didReceiveUpdate = true;
}
//有更新,可是優先級不高,在本次渲染過程當中不須要執行,設爲 false
else if (updateExpirationTime < renderExpirationTime) {
didReceiveUpdate = false;
// This fiber does not have any pending work. Bailout without entering
// the begin phase. There's still some bookkeeping we that needs to be done
// in this optimized path, mostly pushing stuff onto the stack.
//根據workInProgress的tag,進行相應組件的更新
switch (workInProgress.tag) {
case HostRoot:
pushHostRootContext(workInProgress);
resetHydrationState();
break;
case HostComponent:
pushHostContext(workInProgress);
if (
workInProgress.mode & ConcurrentMode &&
renderExpirationTime !== Never &&
shouldDeprioritizeSubtree(workInProgress.type, newProps)
) {
if (enableSchedulerTracing) {
markSpawnedWork(Never);
}
// Schedule this fiber to re-render at offscreen priority. Then bailout.
workInProgress.expirationTime = workInProgress.childExpirationTime = Never;
return null;
}
break;
case ClassComponent: {
const Component = workInProgress.type;
if (isLegacyContextProvider(Component)) {
pushLegacyContextProvider(workInProgress);
}
break;
}
case HostPortal:
pushHostContainer(
workInProgress,
workInProgress.stateNode.containerInfo,
);
break;
case ContextProvider: {
const newValue = workInProgress.memoizedProps.value;
pushProvider(workInProgress, newValue);
break;
}
case Profiler:
if (enableProfilerTimer) {
workInProgress.effectTag |= Update;
}
break;
case SuspenseComponent: {
const state: SuspenseState | null = workInProgress.memoizedState;
const didTimeout = state !== null;
if (didTimeout) {
// If this boundary is currently timed out, we need to decide
// whether to retry the primary children, or to skip over it and
// go straight to the fallback. Check the priority of the primary
// child fragment.
const primaryChildFragment: Fiber = (workInProgress.child: any);
const primaryChildExpirationTime =
primaryChildFragment.childExpirationTime;
if (
primaryChildExpirationTime !== NoWork &&
primaryChildExpirationTime >= renderExpirationTime
) {
// The primary children have pending work. Use the normal path
// to attempt to render the primary children again.
return updateSuspenseComponent(
current,
workInProgress,
renderExpirationTime,
);
} else {
pushSuspenseContext(
workInProgress,
setDefaultShallowSuspenseContext(suspenseStackCursor.current),
);
// The primary children do not have pending work with sufficient
// priority. Bailout.
const child = bailoutOnAlreadyFinishedWork(
current,
workInProgress,
renderExpirationTime,
);
if (child !== null) {
// The fallback children have pending work. Skip over the
// primary children and work on the fallback.
return child.sibling;
} else {
return null;
}
}
} else {
pushSuspenseContext(
workInProgress,
setDefaultShallowSuspenseContext(suspenseStackCursor.current),
);
}
break;
}
case DehydratedSuspenseComponent: {
if (enableSuspenseServerRenderer) {
pushSuspenseContext(
workInProgress,
setDefaultShallowSuspenseContext(suspenseStackCursor.current),
);
// We know that this component will suspend again because if it has
// been unsuspended it has committed as a regular Suspense component.
// If it needs to be retried, it should have work scheduled on it.
workInProgress.effectTag |= DidCapture;
}
break;
}
case SuspenseListComponent: {
const didSuspendBefore =
(current.effectTag & DidCapture) !== NoEffect;
const childExpirationTime = workInProgress.childExpirationTime;
if (childExpirationTime < renderExpirationTime) {
// If none of the children had any work, that means that none of
// them got retried so they'll still be blocked in the same way
// as before. We can fast bail out.
pushSuspenseContext(workInProgress, suspenseStackCursor.current);
if (didSuspendBefore) {
workInProgress.effectTag |= DidCapture;
}
return null;
}
if (didSuspendBefore) {
// If something was in fallback state last time, and we have all the
// same children then we're still in progressive loading state.
// Something might get unblocked by state updates or retries in the
// tree which will affect the tail. So we need to use the normal
// path to compute the correct tail.
return updateSuspenseListComponent(
current,
workInProgress,
renderExpirationTime,
);
}
// If nothing suspended before and we're rendering the same children,
// then the tail doesn't matter. Anything new that suspends will work
// in the "together" mode, so we can continue from the state we had.
let renderState = workInProgress.memoizedState;
if (renderState !== null) {
// Reset to the "together" mode in case we've started a different
// update in the past but didn't complete it.
renderState.rendering = null;
renderState.tail = null;
}
pushSuspenseContext(workInProgress, suspenseStackCursor.current);
break;
}
case EventComponent:
if (enableFlareAPI) {
pushHostContextForEventComponent(workInProgress);
}
break;
}
//跳過該節點及全部子節點的更新
return bailoutOnAlreadyFinishedWork(
current,
workInProgress,
renderExpirationTime,
);
}
} else {
didReceiveUpdate = false;
}
// Before entering the begin phase, clear the expiration time.
workInProgress.expirationTime = NoWork;
//若是節點是有更新的
//根據節點類型進行組件的更新
switch (workInProgress.tag) {
case IndeterminateComponent: {
return mountIndeterminateComponent(
current,
workInProgress,
workInProgress.type,
renderExpirationTime,
);
}
case LazyComponent: {
const elementType = workInProgress.elementType;
return mountLazyComponent(
current,
workInProgress,
elementType,
updateExpirationTime,
renderExpirationTime,
);
}
//FunctionComponent的更新
case FunctionComponent: {
const Component = workInProgress.type;
const unresolvedProps = workInProgress.pendingProps;
const resolvedProps =
workInProgress.elementType === Component
? unresolvedProps
: resolveDefaultProps(Component, unresolvedProps);
return updateFunctionComponent(
current,
workInProgress,
Component,
resolvedProps,
renderExpirationTime,
);
}
//ClassComponent的更新
case ClassComponent: {
const Component = workInProgress.type;
const unresolvedProps = workInProgress.pendingProps;
const resolvedProps =
workInProgress.elementType === Component
? unresolvedProps
: resolveDefaultProps(Component, unresolvedProps);
return updateClassComponent(
current,
workInProgress,
Component,
resolvedProps,
renderExpirationTime,
);
}
case HostRoot:
return updateHostRoot(current, workInProgress, renderExpirationTime);
case HostComponent:
return updateHostComponent(current, workInProgress, renderExpirationTime);
case HostText:
return updateHostText(current, workInProgress);
case SuspenseComponent:
return updateSuspenseComponent(
current,
workInProgress,
renderExpirationTime,
);
case HostPortal:
return updatePortalComponent(
current,
workInProgress,
renderExpirationTime,
);
case ForwardRef: {
const type = workInProgress.type;
const unresolvedProps = workInProgress.pendingProps;
const resolvedProps =
workInProgress.elementType === type
? unresolvedProps
: resolveDefaultProps(type, unresolvedProps);
return updateForwardRef(
current,
workInProgress,
type,
resolvedProps,
renderExpirationTime,
);
}
case Fragment:
return updateFragment(current, workInProgress, renderExpirationTime);
case Mode:
return updateMode(current, workInProgress, renderExpirationTime);
case Profiler:
return updateProfiler(current, workInProgress, renderExpirationTime);
case ContextProvider:
return updateContextProvider(
current,
workInProgress,
renderExpirationTime,
);
case ContextConsumer:
return updateContextConsumer(
current,
workInProgress,
renderExpirationTime,
);
case MemoComponent: {
const type = workInProgress.type;
const unresolvedProps = workInProgress.pendingProps;
// Resolve outer props first, then resolve inner props.
let resolvedProps = resolveDefaultProps(type, unresolvedProps);
if (__DEV__) {
if (workInProgress.type !== workInProgress.elementType) {
const outerPropTypes = type.propTypes;
if (outerPropTypes) {
checkPropTypes(
outerPropTypes,
resolvedProps, // Resolved for outer only
'prop',
getComponentName(type),
getCurrentFiberStackInDev,
);
}
}
}
resolvedProps = resolveDefaultProps(type.type, resolvedProps);
return updateMemoComponent(
current,
workInProgress,
type,
resolvedProps,
updateExpirationTime,
renderExpirationTime,
);
}
case SimpleMemoComponent: {
return updateSimpleMemoComponent(
current,
workInProgress,
workInProgress.type,
workInProgress.pendingProps,
updateExpirationTime,
renderExpirationTime,
);
}
case IncompleteClassComponent: {
const Component = workInProgress.type;
const unresolvedProps = workInProgress.pendingProps;
const resolvedProps =
workInProgress.elementType === Component
? unresolvedProps
: resolveDefaultProps(Component, unresolvedProps);
return mountIncompleteClassComponent(
current,
workInProgress,
Component,
resolvedProps,
renderExpirationTime,
);
}
case DehydratedSuspenseComponent: {
if (enableSuspenseServerRenderer) {
return updateDehydratedSuspenseComponent(
current,
workInProgress,
renderExpirationTime,
);
}
break;
}
case SuspenseListComponent: {
return updateSuspenseListComponent(
current,
workInProgress,
renderExpirationTime,
);
}
case EventComponent: {
if (enableFlareAPI) {
return updateEventComponent(
current,
workInProgress,
renderExpirationTime,
);
}
break;
}
}
invariant(
false,
'Unknown unit of work tag. This error is likely caused by a bug in ' +
'React. Please file an issue.',
);
}
複製代碼
解析:
(1) fiber 對象只要有更新,就會計算出一個 expirationTime
(2) memoizedProps
和pendingProps
是fiber
對象的屬性,具體請看:React源碼解析之RootFiber
(3) switch...case
那段太長了,可跳過,後面會講FunctionComponent
狀況時,組件是如何更新的
(4) 注意updateExpirationTime < renderExpirationTime
的狀況,它的意思是當前 fiber 的更新優先級不高,在本次渲染的過程當中不會執行它的更新,因此會執行bailoutOnAlreadyFinishedWork
,來跳過該節點及全部子節點的更新,再也不往下執行組件的更新
(5) 最後根據workInProgress.tag
的不一樣狀況,來進行組件的更新
4、bailoutOnAlreadyFinishedWork
做用:
跳過該節點及該節點上全部子節點的更新
源碼:
//跳過該節點及全部子節點的更新
function bailoutOnAlreadyFinishedWork(
current: Fiber | null,
workInProgress: Fiber,
renderExpirationTime: ExpirationTime,
): Fiber | null {
//不看
cancelWorkTimer(workInProgress);
if (current !== null) {
// Reuse previous dependencies
workInProgress.dependencies = current.dependencies;
}
if (enableProfilerTimer) {
// Don't update "base" render times for bailouts.
stopProfilerTimerIfRunning(workInProgress);
}
// Check if the children have any pending work.
//expirationTime 表示該節點是否有更新,若是該節點有更新,可能會影響子節點的更新
//若是expirationTime和childExpirationTime都沒有,則子樹是不須要更新的
//因爲子孫節點形成的更新
const childExpirationTime = workInProgress.childExpirationTime;
//若是子樹不須要更新,則返回 null
//childExpirationTime的一個好處就是快捷地知道子樹有沒有更新,從而跳過沒有更新的子樹
//若是childExpirationTime爲空,react 還須要遍歷子樹來判斷是否更新
if (childExpirationTime < renderExpirationTime) {
// The children don't have any work either. We can skip them.
// TODO: Once we add back resuming, we should check if the children are
// a work-in-progress set. If so, we need to transfer their effects.
//跳過整個子樹的更新渲染,這是一個很是大的優化
return null;
}
//調和子節點
else {
// This fiber doesn't have work, but its subtree does. Clone the child
// fibers and continue.
//該節點不須要更新,子節點也不須要更新,因此只要複製子節點過來便可
cloneChildFibers(current, workInProgress);
return workInProgress.child;
}
}
複製代碼
解析:
一般判斷子節點的更新是要遍歷子樹來獲取信息的,但 React 很是聰明地在子節點產生更新的時候,設置上 childExpirationTime,並最終在父節點上設置一個優先級最高的 childExpirationTime,這樣的話,若是childExpirationTime
優先級小於renderExpirationTime
的話,則跳過子樹的遍歷及更新渲染。
這是一個很是大的優化。
5、completeUnitOfWorkcompleteUnitOfWork
跟beginWork
的結構是相似的,但我會放在後面的文章去講
6、workLoop流程圖
7、GitHub
github.com/AttackXiaoJ…
(完)