亚洲激情专区-91九色丨porny丨老师-久久久久久久女国产乱让韩-国产精品午夜小视频观看

溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

React?Fiber樹是怎么構建與更新的

發布時間:2023-01-11 09:20:30 來源:億速云 閱讀:115 作者:iii 欄目:開發技術

這篇文章主要講解了“React Fiber樹是怎么構建與更新的”,文中的講解內容簡單清晰,易于學習與理解,下面請大家跟著小編的思路慢慢深入,一起來研究和學習“React Fiber樹是怎么構建與更新的”吧!

    為什么需要 fiber

    Lin Clark 在 React Conf 2017 的演講中,他通過漫畫的形式,很好地講述了 fiber 為何出現,下面我根據她的演講,結合我自己的理解來談一談 fiber 出現的原因。

    fiber 之前

    在 react15 及之前 fiber 未出現時,react 的一系列執行過程例如生命周期執行、虛擬 dom 的比較、dom 樹的更新等都是同步的,一旦開始執行就不會中斷,直到所有的工作流程全部結束為止。

    要知道,react 所有的狀態更新,都是從根組件開始的,當應用組件樹比較龐大時,一旦狀態開始變更,組件樹層層遞歸開始更新,js 主線程就不得不停止其他工作。例如組件樹一共有 1000 個組件需要更新,每個組件更新所需要的時間為 1s,那么在這 1s 內瀏覽器都無法做其他的事情,用戶的點擊輸入等交互事件、頁面動畫等都不會得到響應,體驗就會非常的差。

    這種情況下,函數堆棧的調用就像下圖一樣,層級很深,很長時間不會返回

    fiber 之后

    為了解決這一問題,react 引入了 fiber 這種數據結構,將更新渲染耗時長的大任務,分為許多的小片。每個小片的任務執行完成后,都先去執行其他高優先級的任務(例如用戶點擊輸入事件、動畫等),這樣 js 的主線程就不會被 react 獨占,雖然任務執行的總時間不變,但是頁面能夠及時響應高優先級任務,顯得不會卡頓了。

    fiber 分片模式下,瀏覽器主線程能夠定期被釋放,保證了渲染的幀率,函數的堆棧調用如下(波谷表示執行分片任務,波峰表示執行其他高優先級任務):

    React?Fiber樹是怎么構建與更新的

    react 通過 fiber,為我們提供了一種跟蹤、調度、暫停和中止工作的便捷方式,保證了頁面的性能和流暢度。

    fiber 節點結構

    fiber 是一種數據結構,每個 fiber 節點的內部,都保存了 dom 相關信息、fiber 樹相關的引用、要更新時的副作用等,我們可以看一下源碼中的 fiber 結構:

    // packages/react-reconciler/src/ReactInternalTypes.jsexport type Fiber = {|  // 作為靜態數據結構,存儲節點 dom 相關信息  tag: WorkTag, // 組件的類型,取決于 react 的元素類型  key: null | string,  elementType: any, // 元素類型  type: any, // 定義與此fiber關聯的功能或類。對于組件,它指向構造函數;對于DOM元素,它指定HTML tag  stateNode: any, // 真實 dom 節點  // fiber 鏈表樹相關  return: Fiber | null, // 父 fiber  child: Fiber | null, // 第一個子 fiber  sibling: Fiber | null, // 下一個兄弟 fiber  index: number, // 在父 fiber 下面的子 fiber 中的下標  ref:    | null    | (((handle: mixed) => void) & {_stringRef: ?string, ...})    | RefObject,  // 工作單元,用于計算 state 和 props 渲染  pendingProps: any, // 本次渲染需要使用的 props  memoizedProps: any, // 上次渲染使用的 props  updateQueue: mixed, // 用于狀態更新、回調函數、DOM更新的隊列  memoizedState: any, // 上次渲染后的 state 狀態  dependencies: Dependencies | null, // contexts、events 等依賴  mode: TypeOfMode,  // 副作用相關  flags: Flags, // 記錄更新時當前 fiber 的副作用(刪除、更新、替換等)狀態  subtreeFlags: Flags, // 當前子樹的副作用狀態  deletions: Array<Fiber> | null, // 要刪除的子 fiber  nextEffect: Fiber | null, // 下一個有副作用的 fiber  firstEffect: Fiber | null, // 指向第一個有副作用的 fiber  lastEffect: Fiber | null, // 指向最后一個有副作用的 fiber   // 優先級相關  lanes: Lanes,  childLanes: Lanes,  alternate: Fiber | null, // 指向 workInProgress fiber 樹中對應的節點  actualDuration?: number,  actualStartTime?: number,  selfBaseDuration?: number,  treeBaseDuration?: number,  _debugID?: number,  _debugSource?: Source | null,  _debugOwner?: Fiber | null,  _debugIsCurrentlyTiming?: boolean,  _debugNeedsRemount?: boolean,  _debugHookTypes?: Array<HookType> | null,|};

    dom 相關屬性

    fiber 中和 dom 節點相關的信息主要關注 tagkeytypestateNode

    tag

    fiber 中 tag 屬性的 ts 類型為 workType,用于標記不同的 react 組件類型,我們可以看一下源碼中 workType 的枚舉值:

    // packages/react-reconciler/src/ReactWorkTags.jsexport const FunctionComponent = 0;export const ClassComponent = 1;export const IndeterminateComponent = 2; // Before we know whether it is function or classexport const HostRoot = 3; // Root of a host tree. Could be nested inside another node.export const HostPortal = 4; // A subtree. Could be an entry point to a different renderer.export const HostComponent = 5;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;export const DehydratedFragment = 18;export const SuspenseListComponent = 19;export const FundamentalComponent = 20;export const ScopeComponent = 21;export const Block = 22;export const OffscreenComponent = 23;export const LegacyHiddenComponent = 24;

    在 react 協調時,beginWork 和 completeWork 等流程時,都會根據 tag 類型的不同,去執行不同的函數處理 fiber 節點。

    key 和 type

    keytype 兩項用于 react diff 過程中確定 fiber 是否可以復用。

    key 為用戶定義的唯一值。type 定義與此fiber關聯的功能或類。對于組件,它指向函數或者類本身;對于DOM元素,它指定HTML tag。

    stateNode

    stateNode用于記錄當前fiber所對應的真實dom節點或者當前虛擬組件的實例,這么做的原因第一是為了實現Ref,第二是為了實現真實 dom的跟蹤。

    鏈表樹相關屬性

    我們看一下和 fiber 鏈表樹構建相關的 returnchildsibling 幾個字段:

    return:指向父 fiber,若沒有父 fiber 則為 null

    child: 指向第一個子 fiber,若沒有任何子 fiber 則為 null

    sibling:指向下一個兄弟 fiber,若沒有下一個兄弟 fiber 則為 null

    通過這幾個字段,各個 fiber 節點構成了 fiber 鏈表樹結構:

    React?Fiber樹是怎么構建與更新的

    副作用相關屬性

    首先理解一下 react 中的副作用,舉一個生活中比較通俗的例子:我們感冒了本來吃點藥就沒事了,但是吃了藥發現身體過敏了,而這個“過敏”就是副作用。react 中,我們修改了 state、props、ref 等數據,除了數據改變之外,還會引起 dom 的變化,這種 render 階段不能完成的工作,我們稱之為副作用。相關參考視頻講解:進入學習

    flags

    react 中通過 flags 記錄每個節點diff后需要變更的狀態,例如 dom 的添加、替換、刪除等等。我們可以看一下源碼中 Flags 枚舉類型:

    例如 Deletion 代表更新時要對 dom 進行刪除,Placement 代表要進行添加或者替換等等。

    // packages/react-reconciler/src/ReactFiberFlags.jsexport type Flags = number;export const NoFlags = /*                      */ 0b000000000000000000;export const PerformedWork = /*                */ 0b000000000000000001;export const Placement = /*                    */ 0b000000000000000010;export const Update = /*                       */ 0b000000000000000100;export const PlacementAndUpdate = /*           */ 0b000000000000000110;export const Deletion = /*                     */ 0b000000000000001000;export const ContentReset = /*                 */ 0b000000000000010000;export const Callback = /*                     */ 0b000000000000100000;export const DidCapture = /*                   */ 0b000000000001000000;export const Ref = /*                          */ 0b000000000010000000;export const Snapshot = /*                     */ 0b000000000100000000;export const Passive = /*                      */ 0b000000001000000000;export const PassiveUnmountPendingDev = /*     */ 0b000010000000000000;export const Hydrating = /*                    */ 0b000000010000000000;export const HydratingAndUpdate = /*           */ 0b000000010000000100;export const LifecycleEffectMask = /*          */ 0b000000001110100100;export const HostEffectMask = /*               */ 0b000000011111111111;export const Incomplete = /*                   */ 0b000000100000000000;export const ShouldCapture = /*                */ 0b000001000000000000;export const ForceUpdateForLegacySuspense = /* */ 0b000100000000000000;export const PassiveStatic = /*                */ 0b001000000000000000;export const BeforeMutationMask = /*           */ 0b000000001100001010;export const MutationMask = /*                 */ 0b000000010010011110;export const LayoutMask = /*                   */ 0b000000000010100100;export const PassiveMask = /*                  */ 0b000000001000001000;export const StaticMask = /*                   */ 0b001000000000000000;export const MountLayoutDev = /*               */ 0b010000000000000000;export const MountPassiveDev = /*              */ 0b100000000000000000;

    Effect List

    在 render 階段時,react 會采用深度優先遍歷,對 fiber 樹進行遍歷,把每一個有副作用的 fiber 篩選出來,最后構建生成一個只帶副作用的 Effect list 鏈表。和該鏈表相關的字段有 firstEffectnextEffectlastEffect

    React?Fiber樹是怎么構建與更新的

    firstEffect 指向第一個有副作用的 fiber 節點,lastEffect 指向最后一個有副作用的節點,中間的節點全部通過 nextEffect 鏈接,最終形成 Effect 鏈表。

    在 commit 階段,React 拿到 Effect list 鏈表中的數據后,根據每一個 fiber 節點的 flags 類型,對相應的 DOM 進行更改。

    其他

    其他需要重點關注一下的屬性還有 lanealternate

    lane

    lane 代表 react 要執行的 fiber 任務的優先級,通過這個字段,render 階段 react 確定應該優先將哪些任務提交到 commit 階段去執行。

    我們看一下源碼中 lane 的枚舉值:

    // packages/react-reconciler/src/ReactFiberLane.jsInputDiscreteHydrationLane: Lane = /*                   */ 0b0000000000000000000000000000100;const InputDiscreteLanes: Lanes = /*                    */ 0b0000000000000000000000000011000;const InputContinuousHydrationLane: Lane = /*           */ 0b0000000000000000000000000100000;const InputContinuousLanes: Lanes = /*                  */ 0b0000000000000000000000011000000;export const DefaultHydrationLane: Lane = /*            */ 0b0000000000000000000000100000000;export const DefaultLanes: Lanes = /*                   */ 0b0000000000000000000111000000000;const TransitionHydrationLane: Lane = /*                */ 0b0000000000000000001000000000000;const TransitionLanes: Lanes = /*                       */ 0b0000000001111111110000000000000;const RetryLanes: Lanes = /*                            */ 0b0000011110000000000000000000000;export const SomeRetryLane: Lanes = /*                  */ 0b0000010000000000000000000000000;export const SelectiveHydrationLane: Lane = /*          */ 0b0000100000000000000000000000000;const NonIdleLanes = /*                                 */ 0b0000111111111111111111111111111;export const IdleHydrationLane: Lane = /*               */ 0b0001000000000000000000000000000;const IdleLanes: Lanes = /*                             */ 0b0110000000000000000000000000000;export const OffscreenLane: Lane = /*                   */ 0b1000000000000000000000000000000;

    同 Flags 的枚舉值一樣,Lanes 也是用 31 位的二進制數表示,表示了 31 條賽道,位數越小的賽道,代表的優先級越高。

    例如 InputDiscreteHydrationLaneInputDiscreteLanesInputContinuousHydrationLane 等用戶交互引起的更新的優先級較高,DefaultLanes 這種請求數據引起更新的優先級中等,而 OffscreenLaneIdleLanes 這種優先級較低。

    優先級越低的任務,在 render 階段越容易被打斷,commit 執行的時機越靠后。

    alternate

    當 react 的狀態發生更新時,當前頁面所對應的 fiber 樹稱為 current Fiber,同時 react 會根據新的狀態構建一顆新的 fiber 樹,稱為 workInProgress Fiber。current Fiber 中每個 fiber 節點通過 alternate 字段,指向 workInProgress Fiber 中對應的 fiber 節點。同樣 workInProgress Fiber 中的 fiber

    節點的 alternate 字段也會指向 current Fiber 中對應的 fiber 節點。

    fiber 樹的構建與更新

    下面我們結合源碼,來看一下實際工作過程中 fiber 樹的構建與更新過程。

    mount 過程

    react 首次 mount 開始執行時,以 ReactDOM.render 為入口函數,會經過如下一系列的函數調用:ReactDOM.render &mdash;&mdash;> legacyRenderSubtreeIntoContainer &mdash;&mdash;> legacyCreateRootFromDOMContainer &mdash;&mdash;> createLegacyRoot &mdash;&mdash;> ReactDOMBlockingRoot &mdash;&mdash;> ReactDOMRoot &mdash;&mdash;> createRootImpl &mdash;&mdash;> createContainer &mdash;&mdash;> createFiberRoot &mdash;&mdash;> createHostRootFiber &mdash;&mdash;> createFiber

    createFiber 函數中,調用 FiberNode 構造函數,創建了 rootFiber,它是 react 應用的根 fiber:

    // packages/react-reconciler/src/ReactFiber.old.jsconst createFiber = function(  tag: WorkTag,  pendingProps: mixed,  key: null | string,  mode: TypeOfMode,): Fiber {  return new FiberNode(tag, pendingProps, key, mode);};

    createFiberRoot 函數中,調用 FiberRootNode 構造函數,創建了 fiberRoot,它指向真實根 dom 節點。

    // packages/react-reconciler/src/ReactFiberRoot.old.jsexport function createFiberRoot(  containerInfo: any,  tag: RootTag,  hydrate: boolean,  hydrationCallbacks: null | SuspenseHydrationCallbacks,): FiberRoot {  const root: FiberRoot = (new FiberRootNode(containerInfo, tag, hydrate): any);  if (enableSuspenseCallback) {    root.hydrationCallbacks = hydrationCallbacks;  }  const uninitializedFiber = createHostRootFiber(tag);  root.current = uninitializedFiber;  uninitializedFiber.stateNode = root;  initializeUpdateQueue(uninitializedFiber);  return root;}

    另外 createFiberRoot 函數中,還讓 rootFiber 的 stateNode 字段指向了 fiberRoot,fiberRoot 的 current 字段指向了 rootFiber。從而一顆最原始的 fiber 樹根節點就創建完成了:

    React?Fiber樹是怎么構建與更新的

    上面的 rootFiber 和 fiberRoot 創建完成后,react 就會根據 jsx 的內容去創建詳細的 dom 樹了,例如有如下的 jsx:

    <div id="root">  <div id="a1">    <div id="b1">      <div id="c1">        <div id="d1"></div>        <div id="d2"></div>        <div id="d3"></div>      </div>      <div id="c2"></div>    </div>  </div></div>

    react 對于 fiber 結構的創建和更新,都是采用深度優先遍歷,從 rootFiber(此處對應id為root的節點)開始,首先創建 child a1,然后發現 a1 有子節點 b1,繼續對 b1 進行遍歷,b1 有子節點 c1,再去創建 c1 的子節點 d1、d2、d3,直至發現 d1、d2、d3 都沒有子節點來了,再回去創建 c2.

    上面的過程,每個節點開始創建時,執行 beginWork 流程,直至該節點的所有子孫節點都創建(更新)完成后,執行 completeWork 流程,過程的圖示如下:

    React?Fiber樹是怎么構建與更新的

    update 過程

    update 時,react 會根據新的 jsx 內容創建新的 workInProgress fiber,還是通過深度優先遍歷,對發生改變的 fiber 打上不同的 flags 副作用標簽,并通過 firstEffectnextEffect 等字段形成 Effect List 鏈表。

    例如上面的 jsx 結構,發生了如下的更新:

    <div id="root">  <div id="a1">    <div id="b1">      <div id="c1">        <div id="d1"></div>-       <div id="d2"></div>-       <div id="d3"></div>      </div>-     <div id="c2"></div>+     <div id="c2">new content</div>    </div>  </div></div>

    react 會根據新的 jsx 解析后的內容,調用 createWorkInProgress 函數創建 workInProgress fiber,對其標記副作用:

    // packages/react-reconciler/src/ReactFiber.old.jsexport function createWorkInProgress(current: Fiber, pendingProps: any): Fiber {  let workInProgress = current.alternate;  if (workInProgress === null) { // 區分 mount 還是 update    workInProgress = createFiber(      current.tag,      pendingProps,      current.key,      current.mode,    );    workInProgress.elementType = current.elementType;    workInProgress.type = current.type;    workInProgress.stateNode = current.stateNode;    if (__DEV__) {      workInProgress._debugID = current._debugID;      workInProgress._debugSource = current._debugSource;      workInProgress._debugOwner = current._debugOwner;      workInProgress._debugHookTypes = current._debugHookTypes;    }    workInProgress.alternate = current;    current.alternate = workInProgress;  } else {    workInProgress.pendingProps = pendingProps;    workInProgress.type = current.type;    workInProgress.subtreeFlags = NoFlags;    workInProgress.deletions = null;    if (enableProfilerTimer) {      workInProgress.actualDuration = 0;      workInProgress.actualStartTime = -1;    }  }  // 重置所有的副作用  workInProgress.flags = current.flags & StaticMask;  workInProgress.childLanes = current.childLanes;  workInProgress.lanes = current.lanes;  workInProgress.child = current.child;  workInProgress.memoizedProps = current.memoizedProps;  workInProgress.memoizedState = current.memoizedState;  workInProgress.updateQueue = current.updateQueue;  // 克隆依賴  const currentDependencies = current.dependencies;  workInProgress.dependencies =    currentDependencies === null      ? null      : {          lanes: currentDependencies.lanes,          firstContext: currentDependencies.firstContext,        };  workInProgress.sibling = current.sibling;  workInProgress.index = current.index;  workInProgress.ref = current.ref;  if (enableProfilerTimer) {    workInProgress.selfBaseDuration = current.selfBaseDuration;    workInProgress.treeBaseDuration = current.treeBaseDuration;  }  if (__DEV__) {    workInProgress._debugNeedsRemount = current._debugNeedsRemount;    switch (workInProgress.tag) {      case IndeterminateComponent:      case FunctionComponent:      case SimpleMemoComponent:        workInProgress.type = resolveFunctionForHotReloading(current.type);        break;      case ClassComponent:        workInProgress.type = resolveClassForHotReloading(current.type);        break;      case ForwardRef:        workInProgress.type = resolveForwardRefForHotReloading(current.type);        break;      default:        break;    }  }  return workInProgress;}

    最終生成的 workInProgress fiber 圖示如下:

    React?Fiber樹是怎么構建與更新的

    然后如上面所說,current fiber 和 workInProgress fiber 中對應的 alternate 會相互指向,然后 workInProgress fiber 完全創建完成后,fiberRoot 的 current 字段的指向會從 current fiber 中的 rootFiber 改為 workInProgress fiber 中的 rootFiber:

    React?Fiber樹是怎么構建與更新的

    感謝各位的閱讀,以上就是“React Fiber樹是怎么構建與更新的”的內容了,經過本文的學習后,相信大家對React Fiber樹是怎么構建與更新的這一問題有了更深刻的體會,具體使用情況還需要大家實踐驗證。這里是億速云,小編將為大家推送更多相關知識點的文章,歡迎關注!

    向AI問一下細節

    免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

    AI

    利川市| 德化县| 宜丰县| 米林县| 河北区| 米脂县| 枞阳县| 聂拉木县| 安达市| 沭阳县| 荣昌县| 盐池县| 阳东县| 措美县| 肇东市| 万载县| 诸城市| 资阳市| 田阳县| 双牌县| 利津县| 祁阳县| 惠安县| 宿迁市| 温宿县| 道孚县| 金乡县| 无锡市| 哈巴河县| 双桥区| 沁水县| 岚皋县| 福安市| 酉阳| 万年县| 姚安县| 全南县| 湘阴县| 安新县| 莒南县| 武宣县|