<ruby id="bdb3f"></ruby>

    <p id="bdb3f"><cite id="bdb3f"></cite></p>

      <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
        <p id="bdb3f"><cite id="bdb3f"></cite></p>

          <pre id="bdb3f"></pre>
          <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

          <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
          <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

          <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                <ruby id="bdb3f"></ruby>

                合規國際互聯網加速 OSASE為企業客戶提供高速穩定SD-WAN國際加速解決方案。 廣告
                [TOC] # 介紹 在詳細介紹 Fiber 之前,先了解一下 Fiber 是什么,以及為什么 React 團隊要話兩年時間重構協調算法。 ## React 的核心思想 **內存中維護一顆虛擬DOM樹,數據變化時(setState),自動更新虛擬 DOM,得到一顆新樹,然后 Diff 新老虛擬 DOM 樹,找到有變化的部分,得到一個 Change(Patch),將這個 Patch 加入隊列,最終批量更新這些 Patch 到 DOM 中**。 ## React 16 之前的不足 首先我們了解一下 React 的工作過程,當我們通過`render()`和 `setState()` 進行組件渲染和更新的時候,React 主要有兩個階段: ![](https://p1-jj.byteimg.com/tos-cn-i-t2oaga2asx/gold-user-assets/2019/6/26/16b9161aa7c10d81~tplv-t2oaga2asx-no-mark:1280:960:0:0.awebp) **調和階段(Reconciler):**[官方解釋](https://link.juejin.cn?target=https%3A%2F%2Fzh-hans.reactjs.org%2Fdocs%2Freconciliation.html "https://zh-hans.reactjs.org/docs/reconciliation.html")。React 會自頂向下通過遞歸,遍歷新數據生成新的 Virtual DOM,然后通過 Diff 算法,找到需要變更的元素(Patch),放到更新隊列里面去。 **渲染階段(Renderer)**:遍歷更新隊列,通過調用宿主環境的API,實際更新渲染對應元素。宿主環境,比如 DOM、Native、WebGL 等。 在協調階段階段,由于是采用的遞歸的遍歷方式,這種也被成為 **Stack Reconciler**,主要是為了區別 **Fiber Reconciler** 取的一個名字。這種方式有一個特點:一旦任務開始進行,就**無法中斷**,那么 js 將一直占用主線程, 一直要等到整棵 Virtual DOM 樹計算完成之后,才能把執行權交給渲染引擎,那么這就會導致一些用戶交互、動畫等任務無法立即得到處理,就會有卡頓,非常的影響用戶體驗。 ## 如何解決之前的不足 > 之前的問題主要的問題是任務一旦執行,就無法中斷,js 線程一直占用主線程,導致卡頓。 可能有些接觸前端不久的不是特別理解上面為什么 js 一直占用主線程就會卡頓,我這里還是簡單的普及一下。 ### 瀏覽器每一幀都需要完成哪些工作? 頁面是一幀一幀繪制出來的,當每秒繪制的幀數(FPS)達到 60 時,頁面是流暢的,小于這個值時,用戶會感覺到卡頓。 1s 60 幀,所以每一幀分到的時間是 1000/60 ≈ 16 ms。所以我們書寫代碼時力求不讓一幀的工作量超過 16ms。 ![image-20190603163205451](https://p1-jj.byteimg.com/tos-cn-i-t2oaga2asx/gold-user-assets/2019/6/26/16b9161aa82b235f~tplv-t2oaga2asx-no-mark:1280:960:0:0.awebp) *瀏覽器一幀內的工作* 通過上圖可看到,一幀內需要完成如下六個步驟的任務: * 處理用戶的交互 * JS 解析執行 * 幀開始。窗口尺寸變更,頁面滾去等的處理 * rAF(requestAnimationFrame) * 布局 * 繪制 如果這六個步驟中,任意一個步驟所占用的時間過長,總時間超過 16ms 了之后,用戶也許就能看到卡頓。 而在上一小節提到的**調和階段**花的時間過長,也就是 js 執行的時間過長,那么就有可能在用戶有交互的時候,本來應該是渲染下一幀了,但是在當前一幀里還在執行 JS,就導致用戶交互不能麻煩得到反饋,從而產生卡頓感。 ### 解決方案 **把渲染更新過程拆分成多個子任務,每次只做一小部分,做完看是否還有剩余時間,如果有繼續下一個任務;如果沒有,掛起當前任務,將時間控制權交給主線程,等主線程不忙的時候在繼續執行。** 這種策略叫做 [Cooperative Scheduling(合作式調度)](https://link.juejin.cn?target=https%3A%2F%2Fwww.w3.org%2FTR%2Frequestidlecallback%2F "https://www.w3.org/TR/requestidlecallback/"),操作系統常用任務調度策略之一。 > **補充知識**,操作系統常用任務調度策略:先來先服務(FCFS)調度算法、短作業(進程)優先調度算法(SJ/PF)、最高優先權優先調度算法(FPF)、高響應比優先調度算法(HRN)、時間片輪轉法(RR)、多級隊列反饋法。 合作式調度主要就是用來分配任務的,當有更新任務來的時候,不會馬上去做 Diff 操作,而是先把當前的更新送入一個 Update Queue 中,然后交給 **Scheduler** 去處理,Scheduler 會根據當前主線程的使用情況去處理這次 Update。為了實現這種特性,使用了`requestIdelCallback`API。對于不支持這個API 的瀏覽器,React 會加上 pollyfill。 在上面我們已經知道瀏覽器是一幀一幀執行的,在兩個執行幀之間,主線程通常會有一小段空閑時間,`requestIdleCallback`可以在這個**空閑期(Idle Period)調用空閑期回調(Idle Callback)**,執行一些任務。 ![image-20190625225130226](https://p1-jj.byteimg.com/tos-cn-i-t2oaga2asx/gold-user-assets/2019/6/26/16b9161aa8ffd22d~tplv-t2oaga2asx-no-mark:1280:960:0:0.awebp) * 低優先級任務由`requestIdleCallback`處理; * 高優先級任務,如動畫相關的由`requestAnimationFrame`處理; * `requestIdleCallback`可以在多個空閑期調用空閑期回調,執行任務; * `requestIdleCallback`方法提供 deadline,即任務執行限制時間,以切分任務,避免長時間執行,阻塞UI渲染而導致掉幀; 這個方案看似確實不錯,但是怎么實現可能會遇到幾個問題: * 如何拆分成子任務? * 一個子任務多大合適? * 怎么判斷是否還有剩余時間? * 有剩余時間怎么去調度應該執行哪一個任務? * 沒有剩余時間之前的任務怎么辦? 接下里整個 Fiber 架構就是來解決這些問題的。 # 什么是 Fiber 為了解決之前提到解決方案遇到的問題,提出了以下幾個目標: * 暫停工作,稍后再回來。 * 為不同類型的工作分配優先權。 * 重用以前完成的工作。 * 如果不再需要,則中止工作。 為了做到這些,我們首先需要一種方法將任務分解為單元。從某種意義上說,這就是 Fiber,Fiber 代表一種**工作單元**。 但是僅僅是分解為單元也無法做到中斷任務,因為函數調用棧就是這樣,每個函數為一個工作,每個工作被稱為**堆棧幀**,它會一直工作,直到堆棧為空,無法中斷。 所以我們需要一種增量渲染的調度,那么就需要重新實現一個堆棧幀的調度,這個堆棧幀可以按照自己的調度算法執行他們。另外由于這些堆棧是可以自己控制的,所以可以加入并發或者錯誤邊界等功能。 因此 Fiber 就是重新實現的堆棧幀,本質上 Fiber 也可以理解為是一個**虛擬的堆棧幀**,將可中斷的任務拆分成多個子任務,通過按照優先級來自由調度子任務,分段更新,從而將之前的同步渲染改為異步渲染。 所以我們可以說 Fiber 是一種數據結構(堆棧幀),也可以說是一種解決可中斷的調用任務的一種解決方案,它的特性就是**時間分片(time slicing)**和**暫停(supense)**。 > 如果了解**協程**的可能會覺得 Fiber 的這種解決方案,跟協程有點像(區別還是很大的),是可以中斷的,可以控制執行順序。在 JS 里的 generator 其實就是一種協程的使用方式,不過顆粒度更小,可以控制函數里面的代碼調用的順序,也可以中斷。 # Fiber 是如何工作的 1. `ReactDOM.render()` 和 `setState` 的時候開始創建更新。 2. 將創建的更新加入任務隊列,等待調度。 3. 在 requestIdleCallback 空閑時執行任務。 4. 從根節點開始遍歷 Fiber Node,并且構建 WokeInProgress Tree。 5. 生成 effectList。 6. 根據 EffectList 更新 DOM。 下面是一個詳細的執行過程圖: ![](https://p1-jj.byteimg.com/tos-cn-i-t2oaga2asx/gold-user-assets/2019/6/26/16b9161aa8463c93~tplv-t2oaga2asx-no-mark:1280:960:0:0.awebp) 1. 第一部分從 `ReactDOM.render()` 方法開始,把接收的 React Element 轉換為 Fiber 節點,并為其設置優先級,創建 Update,加入到更新隊列,這部分主要是做一些初始數據的準備。 2. 第二部分主要是三個函數:`scheduleWork`、`requestWork`、`performWork`,即安排工作、申請工作、正式工作三部曲,React 16 新增的異步調用的功能則在這部分實現,這部分就是 **Schedule 階段**,前面介紹的 Cooperative Scheduling 就是在這個階段,只有在這個解決獲取到可執行的時間片,第三部分才會繼續執行。具體是如何調度的,后面文章再介紹,這是 React 調度的關鍵過程。 3. 第三部分是一個大循環,遍歷所有的 Fiber 節點,通過 Diff 算法計算所有更新工作,產出 EffectList 給到 commit 階段使用,這部分的核心是 beginWork 函數,這部分基本就是 **Fiber Reconciler ,包括 reconciliation 和 commit 階段**。 ## Fiber Node FIber Node,承載了非常關鍵的上下文信息,可以說是貫徹整個創建和更新的流程,下來分組列了一些重要的 Fiber 字段。 ~~~ { ... // 跟當前Fiber相關本地狀態(比如瀏覽器環境就是DOM節點) stateNode: any, // 單鏈表樹結構 return: Fiber | null,// 指向他在Fiber節點樹中的`parent`,用來在處理完這個節點之后向上返回 child: Fiber | null,// 指向自己的第一個子節點 sibling: Fiber | null, // 指向自己的兄弟結構,兄弟節點的return指向同一個父節點 // 更新相關 pendingProps: any, // 新的變動帶來的新的props memoizedProps: any, // 上一次渲染完成之后的props updateQueue: UpdateQueue<any> | null, // 該Fiber對應的組件產生的Update會存放在這個隊列里面 memoizedState: any, // 上一次渲染的時候的state // Scheduler 相關 expirationTime: ExpirationTime, // 代表任務在未來的哪個時間點應該被完成,不包括他的子樹產生的任務 // 快速確定子樹中是否有不在等待的變化 childExpirationTime: ExpirationTime, // 在Fiber樹更新的過程中,每個Fiber都會有一個跟其對應的Fiber // 我們稱他為`current <==> workInProgress` // 在渲染完成之后他們會交換位置 alternate: Fiber | null, // Effect 相關的 effectTag: SideEffectTag, // 用來記錄Side Effect nextEffect: Fiber | null, // 單鏈表用來快速查找下一個side effect firstEffect: Fiber | null, // 子樹中第一個side effect lastEffect: Fiber | null, // 子樹中最后一個side effect .... }; 復制代碼 ~~~ ## Fiber Reconciler 在第二部分,進行 Schedule 完,獲取到時間片之后,就開始進行 reconcile。 Fiber Reconciler 是 React 里的調和器,這也是任務調度完成之后,如何去執行每個任務,如何去更新每一個節點的過程,對應上面的第三部分。 reconcile 過程分為2個階段(phase): 1. (可中斷)render/reconciliation 通過構造 WorkInProgress Tree 得出 Change。 2. (不可中斷)commit 應用這些DOM change。 ### reconciliation 階段 在 reconciliation 階段的每個工作循環中,每次處理一個 Fiber,處理完可以中斷/掛起整個工作循環。通過每個節點更新結束時向上歸并 **Effect List** 來收集任務結果,reconciliation 結束后,**根節點**的 Effect List里記錄了包括 DOM change 在內的所有 **Side Effect**。 render 階段可以理解為就是 Diff 的過程,得出 Change(Effect List),會執行聲明如下的聲明周期方法: * \[UNSAFE\_\]componentWillMount(棄用) * \[UNSAFE\_\]componentWillReceiveProps(棄用) * getDerivedStateFromProps * shouldComponentUpdate * \[UNSAFE\_\]componentWillUpdate(棄用) * render 由于 reconciliation 階段是可中斷的,一旦中斷之后恢復的時候又會重新執行,所以很可能 reconciliation 階段的生命周期方法會被多次調用,所以在 reconciliation 階段的生命周期的方法是不穩定的,我想這也是 React 為什么要廢棄 `componentWillMount` 和 `componentWillReceiveProps`方法而改為靜態方法 `getDerivedStateFromProps` 的原因吧。 ### commit 階段 commit 階段可以理解為就是將 Diff 的結果反映到真實 DOM 的過程。 在 commit 階段,在 commitRoot 里會根據 `effect`的 `effectTag`,具體 effectTag 見[源碼](https://link.juejin.cn?target=https%3A%2F%2Fgithub.com%2Ffacebook%2Freact%2Fblob%2F504576306461a5ff339dc99691842f0f35a8bf4c%2Fpackages%2Fshared%2FReactSideEffectTags.js "https://github.com/facebook/react/blob/504576306461a5ff339dc99691842f0f35a8bf4c/packages/shared/ReactSideEffectTags.js") ,進行對應的插入、更新、刪除操作,根據 `tag` 不同,調用不同的更新方法。 commit 階段會執行如下的聲明周期方法: * getSnapshotBeforeUpdate * componentDidMount * componentDidUpdate * componentWillUnmount > P.S:注意區別 reconciler、reconcile 和 reconciliation,reconciler 是調和器,是一個名詞,可以說是 React 工作的一個模塊,協調模塊;reconcile 是調和器調和的動作,是一個動詞;而 reconciliation 只是 reconcile 過程的第一個階段。 ## Fiber Tree 和 WorkInProgress Tree React 在 render 第一次渲染時,會通過 React.createElement 創建一顆 Element 樹,可以稱之為 **Virtual DOM Tree**,由于要記錄上下文信息,加入了 Fiber,每一個 Element 會對應一個 Fiber Node,將 Fiber Node 鏈接起來的結構成為 **Fiber Tree**。它反映了用于渲染 UI 的應用程序的狀態。這棵樹通常被稱為 **current 樹(當前樹,記錄當前頁面的狀態)。** 在后續的更新過程中(setState),每次重新渲染都會重新創建 Element, 但是 Fiber 不會,Fiber 只會使用對應的 Element 中的數據來更新自己必要的屬性, Fiber Tree 一個重要的特點是鏈表結構,將遞歸遍歷編程循環遍歷,然后配合 requestIdleCallback API, 實現任務拆分、中斷與恢復。 這個鏈接的結構是怎么構成的呢,這就要主要到之前 Fiber Node 的節點的這幾個字段: ~~~ // 單鏈表樹結構 { return: Fiber | null, // 指向父節點 child: Fiber | null,// 指向自己的第一個子節點 sibling: Fiber | null,// 指向自己的兄弟結構,兄弟節點的return指向同一個父節點 } ~~~ 每一個 Fiber Node 節點與 Virtual Dom 一一對應,所有 Fiber Node 連接起來形成 Fiber tree, 是個單鏈表樹結構,如下圖所示: ![](https://p1-jj.byteimg.com/tos-cn-i-t2oaga2asx/gold-user-assets/2019/6/26/16b9161aa9292a8a~tplv-t2oaga2asx-no-mark:1280:960:0:0.awebp) 對照圖來看,是不是可以知道 Fiber Node 是如何聯系起來的呢,Fiber Tree 就是這樣一個單鏈表。 **當 render 的時候有了這么一條單鏈表,當調用 `setState` 的時候又是如何 Diff 得到 change 的呢?** 采用的是一種叫**雙緩沖技術(double buffering)**,這個時候就需要另外一顆樹:WorkInProgress Tree,它反映了要刷新到屏幕的未來狀態。 WorkInProgress Tree 構造完畢,得到的就是新的 Fiber Tree,然后喜新厭舊(把 current 指針指向WorkInProgress Tree,丟掉舊的 Fiber Tree)就好了。 這樣做的好處: * 能夠復用內部對象(fiber) * 節省內存分配、GC的時間開銷 * 就算運行中有錯誤,也不會影響 View 上的數據 每個 Fiber上都有個`alternate`屬性,也指向一個 Fiber,創建 WorkInProgress 節點時優先取`alternate`,沒有的話就創建一個。 創建 WorkInProgress Tree 的過程也是一個 Diff 的過程,Diff 完成之后會生成一個 Effect List,這個 Effect List 就是最終 Commit 階段用來處理副作用的階段。 <br> <br> # 參考資料 * [Deep In React 之淺談 React Fiber 架構(一)](https://juejin.cn/post/6844903874692661255)
                  <ruby id="bdb3f"></ruby>

                  <p id="bdb3f"><cite id="bdb3f"></cite></p>

                    <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
                      <p id="bdb3f"><cite id="bdb3f"></cite></p>

                        <pre id="bdb3f"></pre>
                        <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

                        <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
                        <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

                        <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                              <ruby id="bdb3f"></ruby>

                              哎呀哎呀视频在线观看