React源碼解析系列文章歡迎閱讀:
React16源碼解析(一)- 圖解Fiber架構
React16源碼解析(二)-創建更新
React16源碼解析(三)-ExpirationTime
React16源碼解析(四)-Scheduler
React16源碼解析(五)-更新流程渲染階段1
React16源碼解析(六)-更新流程渲染階段2
React16源碼解析(七)-更新流程渲染階段3
React16源碼解析(八)-更新流程提交階段
正在更新中...
上篇文章講解到renderRoot & completeRoot。
React把組件的更新過程分爲了兩個階段:渲染階段(renderRoot)和提交階段(completeRoot)。
本文討論渲染階段。
從上篇文章末尾的renderRoot講起:
renderRoot
1、nextUnitOfWork = createWorkInProgress() 拷貝一份 fiber 節點,在 nextUnitOfWork 中修改,防止改變當前 fiberTree。nextUnitOfWork 是下一個要更新的節點。
2、進入workLoop。
// 開始渲染整顆樹,這個函數在異步模式下可能會被多次執行,因爲在異步模式下
// 可以打斷任務。打斷也就意味着每次都得回到 root 再開始從上往下循環
function renderRoot(
root: FiberRoot,
isYieldy: boolean,
isExpired: boolean,
): void {
isWorking = true;
ReactCurrentOwner.currentDispatcher = Dispatcher;
const expirationTime = root.nextExpirationTimeToWorkOn;
// 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(
// FiberRoot 對應的 Rootfiber
nextRoot.current,
null,
nextRenderExpirationTime,
);
root.pendingCommitExpirationTime = NoWork;
// ......
}
// ......
do {
try {
// 循環更新節點
workLoop(isYieldy);
} catch (thrownValue) {
// 遇到某種錯誤
break;
} while (true);
// 這裏有一大堆的錯誤處理
// Ready to commit.
onComplete(root, rootWorkInProgress, expirationTime);
}
workLoop
循環單元更新,對整顆 fiberTree 都遍歷一遍。
還記得之前傳入進來的isYieldy的麼,如果爲false,不可中斷,不斷的更新下一個節點任務(performUnitOfWork(nextUnitOfWork)),知道整棵樹更新完畢。如果可以中斷,通過shouldYield()判斷當前幀是否還有時間更新,有時間就更新,沒有時間了就不更了。
注:在workLoop 跟新後會有各種錯誤處理。
function workLoop(isYieldy) {
// 對 nextUnitOfWork 循環進行判斷,直到沒有 nextUnitOfWork
if (!isYieldy) {
// 不可中斷
// Flush work without yielding
while (nextUnitOfWork !== null) {
// 一開始進來 nextUnitOfWork 是 root,每次執行 performUnitOfWork 後
// 都會生成下一個工作單元
nextUnitOfWork = performUnitOfWork(nextUnitOfWork);
}
} else {
// 可中斷
// Flush asynchronous work until the deadline runs out of time.
while (nextUnitOfWork !== null && !shouldYield()) {
nextUnitOfWork = performUnitOfWork(nextUnitOfWork);
}
}
}
performUnitOfWork
1、調用beginWork()更新當前任務節點
2、如果當前fiber樹已經更新到葉子節點了,則調用completeUnitOfWork更新。
// 開始組件更新
function performUnitOfWork(workInProgress: 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.
// 獲得 fiber 的替身,調和這一階段都是在替身上完成的
// 然後直接看 beginWork
const current = workInProgress.alternate;
// ......
let next;
// .....
// 開始工作
next = beginWork(current, workInProgress, nextRenderExpirationTime);
workInProgress.memoizedProps = workInProgress.pendingProps;
// ......
// 當前fiber樹已經更新到葉子節點了
if (next === null) {
// If this doesn't spawn new work, complete the current work.
next = completeUnitOfWork(workInProgress);
}
ReactCurrentOwner.current = null;
return next;
}
beginWork
通過workInProgress.tag判斷當前是什麼類型的節點。
workInProgress.tag的所有類型定義在ReactWorkTags.js裏面
export const FunctionComponent = 0; //函數式組件
export const ClassComponent = 1; //class類組件
export const IndeterminateComponent = 2; // 並不確定是函數組件還是類組件
export const HostRoot = 3; // 組件樹根組件,可以嵌套
export const HostPortal = 4; // 子樹. Could be an entry point to a different renderer.
export const HostComponent = 5;// 標準組件,如地div, span等
export const HostText = 6;// 文本
export const Fragment = 7;// 片段
export const Mode = 8;
export const ContextConsumer = 9;
export const ContextProvider = 10;
export const ForwardRef = 11;
export const Profiler = 12;
export const SuspenseComponent = 13;
export const MemoComponent = 14;
export const SimpleMemoComponent = 15;
export const LazyComponent = 16;
export const IncompleteClassComponent = 17;
接下來我會依次講解下面這些組件的更新:
1、FunctionComponent(函數組件),return updateFunctionComponent(…)。
2、ClassComponent(class類組件) return updateClassComponent(…)
3、IndeterminateComponent(不確定是函數組件還是類組件),return mountIndeterminateComponent(…)
4、HostRoot(組件樹根組件),return updateHostRoot(…);
5、HostComponent(標準dom節點),return updateHostComponent(…);
6、HostText(文本),updateHostText(…);
7、HostPortal,updatePortalComponent(…)
8、ForwardRef,updateForwardRef(…)
9、Mode,updateMode(…)
10、MemoComponent,updateMemoComponent(….)
function beginWork(
current: Fiber | null,
workInProgress: Fiber,
renderExpirationTime: ExpirationTime,
): Fiber | null {
const updateExpirationTime = workInProgress.expirationTime;
if (current !== null) {
const oldProps = current.memoizedProps;
const newProps = workInProgress.pendingProps;
// 判斷 props 和 context 是否改變
// 判斷當前 fiber 的優先級是否小於本次渲染的優先級,小於的話可以跳過
if (
oldProps === newProps &&
!hasLegacyContextChanged() &&
(updateExpirationTime === NoWork ||
updateExpirationTime > renderExpirationTime)
) {
// ......
}
// bailoutOnAlreadyFinishedWork 會判斷這個 fiber 的子樹是否需要更新,如果有需要更新會 clone 一份到 workInProgress.child 返回到 workLoop 的 nextUnitOfWork, 否則爲 null
return bailoutOnAlreadyFinishedWork(
current,
workInProgress,
renderExpirationTime,
);
}
}
// Before entering the begin phase, clear the expiration time.
// 進行更新先把當前 fiber 的 expirationTime 設置爲 NoWork
workInProgress.expirationTime = NoWork;
// 根據 fiber 的 tag 類型進行更新
switch (workInProgress.tag) {
case IndeterminateComponent: ......
case LazyComponent: ......
case FunctionComponent: ......
case ClassComponent: ......
case HostRoot:......
case HostComponent:......
case HostText:......
case SuspenseComponent:......
case HostPortal:......
case ForwardRef: ......
case Fragment:......
case Mode:......
case Profiler:......
case ContextProvider:......
case ContextConsumer:......
case MemoComponent: ......
case SimpleMemoComponent: ......
case IncompleteClassComponent:......
default:
invariant(
false,
'Unknown unit of work tag. This error is likely caused by a bug in ' +
'React. Please file an issue.',
);
}
}
下篇文章繼續講解這些組件的更新。
任世界紛繁複雜,仍舊保持可愛。
我是小柚子小仙女。文章如有不妥,歡迎指正~