日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

5渲染判断if_React 16 渲染流程

發布時間:2024/9/19 编程问答 31 豆豆
生活随笔 收集整理的這篇文章主要介紹了 5渲染判断if_React 16 渲染流程 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

學過微機的同學都應該很熟悉「中斷」這個概念:

  • CPU 正常運行程序時,內部事件或外設提出中斷請求;
  • CPU 予以響應,同時保護好 CPU 執行主程序的現場,轉入調用中斷服務程序;
  • 調用完畢后恢復現場。

本文學習的 React 源碼版本:16.9.0

Fiber 調度/渲染

Stack Reconciler

React 16 之前的組件渲染方式是遞歸渲染:渲染父節點 -> 渲染子節點

遞歸渲染看起來十分簡單,但是如果想在子節點的渲染過程中執行優先級更高的操作,只能保留調用棧中子節點的渲染及子節點之前節點的渲染,這樣是很復雜的,這種調和/渲染也叫做 Stack Reconciler。

Fiber Reconciler

Fiber 使用鏈表的結構去渲染節點,每一個節點都稱之為 Fiber Node,每個節點會有三個屬性:

  • child 指向第一個子節點
  • sibling 指向兄弟節點
  • return 指向父節點

Fiber 的渲染方式:從父節點開始,向下依次遍歷子節點,深度優先渲染完子節點后,再回到其父節點去檢查是否有兄弟節點,如果有兄弟節點,則從該兄弟節點開始繼續深度優先的渲染,直到回退到根節點結束。

重復遍歷的節點并不會重復渲染,而是為了取到下一個可能需要渲染的節點。

此時每一個節點都是一個渲染任務, 從而將整個界面渲染任務拆分成更小的模塊,渲染可拆分就意味著每次任務執行前都可以檢查是否去執行優先級更高的操作。


Fiber Node Tree

實際上,真實的 DOM 渲染過程是 diff 兩棵 Fiber 節點樹得到 effect list,在 commit 階段執行。在 React16 中,兩棵樹分別是:

  • current tree (在源碼中即 HostRoot.current)
  • workInProgress tree(在源碼中即 HostRoot.current.alternate)

不過 React 并沒有實現兩棵 Fiber Node Tree,實際情況是兩棵樹上對應的 Fiber Node 通過 alternate 屬性互相引用。

// packages/react-reconiler/src/ReactFiber.js // This is used to create an alternate fiber to do work on. export function createWorkInProgress() {// ...workInProgress.pendingProps = pendingProps;workInProgress.child = current.child;workInProgress.sibling = current.sibling;// ... }

React 渲染流程

可以分為 Scheduler、Reconciliation、Commit 這三個階段

Scheduler 階段

Scheduer 流程主要是創建更新,創建更新的方式:

  • ReactDOM.render
  • setState

可以發現 React 將首次渲染和更新渲染統一了起來。

ReactDOM.render

調用 legacyRenderSubtreeIntoContainer

// packages/react-dom/src/client/ReactDOM.js const ReactDOM = {render() {// ...return legacyRenderSubtreeIntoContainer(null,element,container,false,callback,);} }

legacyRenderSubtreeIntoContainer

調用 root.render,root 來自調用 legacyCreateRootFromDOMContainer。

// packages/react-dom/src/client/ReactDOM.js function legacyRenderSubtreeIntoContainer() {let root: _ReactSyncRoot = (container._reactRootContainer: any);if (!root) {// Initial mountroot = container._reactRootContainer = legacyCreateRootFromDOMContainer(container,forceHydrate,);} else {// UpdateupdateContainer(children, fiberRoot, parentComponent, callback);} }

legacyCreateRootFromDOMContainer

  • 清除根節點下的所有子元素
  • 創建 ReactRoot
// packages/react-dom/src/client/ReactDOM.js function legacyCreateRootFromDOMContainer() {const shouldHydrate =forceHydrate || shouldHydrateDueToLegacyHeuristic(container);if (!shouldHydrate) {let rootSibling;while ((rootSibling = container.lastChild)) {container.removeChild(rootSibling);}}return new ReactSyncRoot(container, LegacyRoot, shouldHydrate); }

ReactSyncRoot

后面就是創建 FiberRoot 了,不放源碼上來了。


setState

enqueueUpdate

將當前的更新壓入更新隊列

// packages/src/react-reconciler/src/ReactFiberClassComponent.js const updater = {enqueueUpdate(inst, payload, callback) {const fiber = getInstance(inst);const currentTime = requestCurrentTime();const suspenseConfig = requestCurrentSuspenseConfig();// 到期時間(Fiber 中的優先級)const expirationTime = computeExpirationForFiber(currentTime,fiber,suspenseConfig,);const update = createUpdate(expirationTime, suspenseConfig);update.payload = payload; // payload 為 setState 中的更新對象if (callback !== undefined && callback !== null) {if (__DEV__) {warnOnInvalidCallback(callback, 'setState');}update.callback = callback;}enqueueUpdate(fiber, update); // 更新信息放入 updateQueuescheduleWork(fiber, expirationTime); // 并行渲染的核心:Scheduler} }

scheduleWork

// packages/react-reconciler/src/ReactFiberWOrkLoop.js function scheduleUpdateOnFiber() {// ... next }

scheduler 的具體過程會在之后的并行渲染中展開。


Reconciliation 階段

workLoop

循環更新,對整棵 Fiber 樹都遍歷一遍。

循環每渲染完成一個 Fiber Node 就利用 shouldYield 來判斷是否有優先級更高的任務存在,是則跳出循環先執行優先級更高的任務,否則繼續渲染下一個 Fiber Node。

還記得文章一開始介紹了微機中的中斷概念么,可以看到在 workLoop 過程中體現出來了。

簡單來說就是判斷當前幀是否還有時間更新,如果沒有時間更新就將剩余時間去進行其他操作。

// packages/react-reconciler/src/ReactFiberWorkLoop.js function renderRoot() {do {try {if (isSync) {workLoopSync();} else {workLoop(); }break;}// ... }function workLoop() {while (workInProgress !== null && !shouldYield()) {workInProgress = performUnitOfWork(workInProgress);} }

performUnitOfWork

調用 beginWork 更新當前任務節點,如果 Fiber 樹已經更新到葉子節點,則調用 completeUnitOfWork 更新。

// packages/react-reconciler/src/ReactFiberWorkLoop.js function performUnitOfWork(unitOfWork: Fiber): Fiber | null {// 調和階段都是在 alternate 上完成const current = unitOfWork.alternate;// ...next = beginWork(current, unitOfWork, renderExpirationTime);unitOfWork.memoizedProps = unitOfWork.pendingProps;// Fiber 樹已經更新到葉子節點if (next === null) {next = completeUnitOfWork(unitOfWork);}return next; }

beginWork

根據 workInProgress 的 tag ,把對應的 FiberNode 上下文壓入棧,然后更新節點,對應 render 階段。

// packages/react-reconciler/src/ReactFiberBeginWork.js function beginWork() {...switch (workInProgress.tag) {case ClassComponent: {const Component = workInProgress.type;if (isLegacyContextProvider(Component)) {pushLegacyContextProvider(workInProgress); // 入棧}break;}} }

beginWork 會返回當前節點的子節點,如果有子節點,繼續 workLoop;如果沒有子節點,進入 completeUnitOfWork

子節點的 alternate 改變是在 cloneChildFibers 函數中

completeWork

改變 effectList(firstEffect、lastEffect、nextEffect)

作用是將 Fiber Node 上下文出棧,對應 commit 階段

// packages/react-reconciler/ReactFiberCompleteWork.js function completeWork() {const newProps = workInProgress.pendingProps;switch (workInProgress.tag) {case ClassComponent: {const Component = workInProgress.type;if (isLegacyContextProvider(Component)) {popLegacyContext(workInProgress); // 出棧}break;}} }

commit 階段

從字面意思來就可以知道, commit 階段是將調和階段的更新進行提交,即把更新操作反映到真實的 DOM 上。

同時,commit 階段是同步執行,不可被中斷。

Effect

函數式編程中經常會看見 Effect 這個概念,表示副作用。在 Fiber 架構中,Effect 定義了 Fiber Node 在 commit 階段要做的事情,在源碼中也就是 EffectTag 這個屬性。

  • 對于組件:更新 refs、調用 componentDidUpdate...
  • 對于 DOM:增加、更新、刪除 DOM...

Effect 組成的鏈表成為 effects list

  • firstEffect:指向第一個更新的節點
  • nextEffect:指向下一個更新的節點

commitRoot

使 effects list 生效:

  • 第一次遍歷 effects list(commitBeforeMutationEffects):在更改前讀取 DOM 上的 state,這里是 getSnapshotBeforeUpdate 生命周期調用的地方;
  • 第二次遍歷 effects list(commitMutationEffects):此階段是真正更改 DOM 的階段;
  • 第三次遍歷 effects list(commitLayoutEffects):執行生命周期函數 componentDidMount、componentDidUpdate...
// packages/react-reconciler/src/ReactFiberWorkLoop.js function commitRootImpl() {// ...// 三次遍歷 effects listdo {commitBeforeMutationEffects();} while (nextEffect !== null);do {commitMutationEffects(renderPriorityLevel);} while (nextEffect !== null);do {commitLayoutEffects(root, expirationTime);} while (nextEffect !== null); }

commitBeforeMutationEffects

  • 通過 prevProps、prevState 以獲取 Snapshot;
  • 調用組件實例的 getSnapshotBeforeUpdate,返回值用于 componentDidUpdate 的第三個參數。
// packages/react-reconciler/src/ReactFiberCommitWork.js function commitBeforeMutationLifeCycles() {// ...switch (finishedWork.tag) {case ClassComponent: {if (current !== null) {// pervProps、pervStateconst prevProps = current.memoizedProps;const prevState = current.memoizedState;startPhaseTimer(finishedWork, 'getSnapshotBeforeUpdate');// getSnapshotBeforeUpdateconst snapshot = instance.getSnapshotBeforeUpdate(finishedWork.elementType === finishedWork.type? prevProps: resolveDefaultProps(finishedWork.type, prevProps),prevState,);}}} }

commitMutationEffects

根據不同的 effectTag 執行不同的操作:

  • 插入節點:commitPlacement
  • 更新節點:commitWork
  • 刪除節點:commitDeletion
// packages/react-reconciler/src/ReactFiberWorkLoop.js function commitMutationEffects() {// ...switch (primaryEffectTag) {case Placement: {commitPlacement(nextEffect);break;}case PlacementAndUpdate: {commitPlacement(nextEffect);// Updateconst current = nextEffect.alternate;commitWork(current, nextEffect);break;}case Update: {const current = nextEffect.alternate;commitWork(current, nextEffect);break;}case Deletion: {commitDeletion(nextEffect, renderPriorityLevel);break;} } }

commitPlacement -- 插入節點

  • 找到 finishedWork 的父節點 parentFiber。尋找的是原生的 DOM 節點對應的 Fiber Node,如果父級不是原生 DOM,則繼續往上尋找。
  • 找到待插入節點的后一個節點
  • 使用 insertBefore 或 appendChild 或深度優先遍歷 class 組件的子節點插入
// packages/react-reconciler/src/ReactFiberCommitWork.js function commitPlacement {// 遞歸插入所有節點到父節點const parentFiber = getHostParentFiber(finishedWork);// 找待插入節點的后一個節點const before = getHostSibling(finishedWork);insertBefore(parent, stateNode, before);// or...appendChild(parent, stateNode);// or...while (node.sibling === null) {if (node.return === null || node.return === finishedWork) {return;}node = node.return;} }

commitWork -- 更新節點

commitWork 只會對 HostComponent 和 HostText 進行更新,也就是 DOM 節點和文本節點。

  • HostComponent 調用 commitUpdate
  • HostText 調用 commitTextUpdate
// packages/react-reconciler/src/ReactFiberCommitWork.js function commitWork() {switch (finishedWork.tag) {case HostComponent: {commitUpdate(instance,updatePayload,type,oldProps,newProps,finishedWork,);}case HostText: {commitTextUpdate(textInstance, oldText, newText);}} }

commitUpdate

updatePayload 應用到真實 DOM 上;對一些屬性做特殊處理

// packages/react-dom/src/client/ReactDOMHostConfig.js function commitUpdate() {updateFiberProps(domElement, newProps);updateProperties(domElement, updatePayload, type, oldProps, newProps); }// packages/react-dom/src/client/ReactDOMComponent.js function updateDOMProperties(domElement: Element,updatePayload: Array<any>,wasCustomComponentTag: boolean,isCustomComponentTag: boolean, ): void {for (let i = 0; i < updatePayload.length; i += 2) {const propKey = updatePayload[i];const propValue = updatePayload[i + 1];if (propKey === STYLE) {setValueForStyles(domElement, propValue);} else if (propKey === DANGEROUSLY_SET_INNER_HTML) {setInnerHTML(domElement, propValue);} else if (propKey === CHILDREN) {setTextContent(domElement, propValue);} else {setValueForProperty(domElement, propKey, propValue, isCustomComponentTag);}} }

commitTextUpdate

文本更新很簡單,直接替換 value

// packages/react-dom/src/client/ReactDOMHostConfig.js function commitTextUpdate() {textInstance.nodeValue = newText; }

commitDeletion -- 刪除節點

刪除節點也需要考慮到子節點不一定是原生 DOM 的情況,比如如果是 class Component,需要調用 componentWillUnmount,所以還是需要深度遍歷整個子樹

// packages/react-reconciler/src/ReactFiberCommitWork.jsfunction commitDeletion(current: Fiber,renderPriorityLevel: ReactPriorityLevel, ): void {if (supportsMutation) {unmountHostComponents(current, renderPriorityLevel);} else {// Detach refs and call componentWillUnmount() on the whole subtree.commitNestedUnmounts(current, renderPriorityLevel);}detachFiber(current); }

commitLayoutEffects

  • 執行 componentDidMount、componentDidUpdate
// packges/react-reconciler/src/ReactFiberWorkLoop.js function commitLayoutEffects() {while (nextEffect !== null) {if (effectTag & (Update | Callback)) {recordEffect();const current = nextEffect.alternate;commitLifeCycles(root,current,nextEffect,committedExpirationTime,);}} } // packages/react-reconciler/src/ReactFiberCommitWork.js function commitLifeCycles() {switch (finishedWork.tag) {// ...case ClassComponent: {const instance = finishedWork.stateNode;if (finishedWork.effectTag & Update) {if (current === null) {startPhaseTimer(finishedWork, 'componentDidMount');} else {const prevProps =finishedWork.elementType === finishedWork.type? current.memoizedProps: resolveDefaultProps(finishedWork.type, current.memoizedProps);const prevState = current.memoizedState;startPhaseTimer(finishedWork, 'componentDidUpdate');}}}} }

參考

  • 探究 React Work Loop 原理

總結

以上是生活随笔為你收集整理的5渲染判断if_React 16 渲染流程的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。