精讀《Scheduling in React》

1. 引言

此次介紹的文章是 scheduling-in-react,簡單來講就是 React 的調度系統,爲了獲得更順滑的用戶體驗。html

畢竟前端作到最後,都是體驗優化,前端帶給用戶的價值核心就在於此。前端

2. 概述

文章從 Dan 在 JSConf 提到的 Demo 提及:react

這是一個測試性能的 Demo,隨着輸入框字符的增長,下方圖表展現的數據量會急速提高。在 Synchronous 與 Debounced 模式下的效果都不盡如人意,只有 Concurrent 模式下看起來是順暢的。git

那麼爲何普通的 Demo 會很卡呢?github

這就涉及到瀏覽器 Event Loop 規則了。npm

JS 是單線程的,瀏覽器同一時間只能作一件事情,而肉眼能識別的刷新頻率在 60FPS 左右,這意味着咱們須要在 16ms 以內完成 Demo 中的三件事:響應用戶輸入,作動畫,Dom 渲染。瀏覽器

然而目前幾乎全部框架都使用同步渲染模式,這意味着若是一個渲染函數執行時間超過了 16ms,則不可避免的發生卡頓。微信

總結一下有兩個主要問題:架構

  1. 長時間運行的任務形成頁面卡頓,咱們須要保證全部任務能在幾毫秒內完成,這樣才能保證頁面的流暢。
  2. 不一樣任務優先級不一樣,好比響應用戶輸入的任務優先級就高於動畫。這個很好理解。

React 調度機制

爲了解決這個問題,React16 經過 Concurrent(並行渲染) 與 Scheduler(調度)兩個角度解決問題:框架

  • Concurrent: 將同步的渲染變成可拆解爲多步的異步渲染,這樣能夠將超過 16ms 的渲染代碼分幾回執行。
  • Scheduler: 調度系統,支持不一樣渲染優先級,對 Concurrent 進行調度。固然,調度系統對低優先級任務會不斷提升優先級,因此不會出現低優先級任務總得不到執行的狀況。

爲了保證不產生阻塞的感受,調度系統會將全部待執行的回調函數存在一份清單中,在每次瀏覽器渲染時間分片間儘量的執行,並將沒有執行完的內容 Hold 住留到下個分片處理。

Concurrent 的正式 API 會在 2019 Q2 發佈,如今能夠經過 <React.unstable_ConcurrentMode> API 方式調用:

ReactDOM.render(
  <React.unstable_ConcurrentMode> <App /> </React.unstable_ConcurrentMode>, rootElement ); 複製代碼

只申明這個是不夠的,由於咱們尚未申明各函數執行的優先級。咱們能夠經過 npm i scheduler 包來申明函數的優先級:

import { unstable_next } from "scheduler";

function SearchBox(props) {
  const [inputValue, setInputValue] = React.useState();

  function handleChange(event) {
    const value = event.target.value;

    setInputValue(value);
    unstable_next(function() {
      props.onChange(value);
      sendAnalyticsNotification(value);
    });
  }

  return <input type="text" value={inputValue} onChange={handleChange} />; } 複製代碼

unstable_next() 做用域下的代碼優先級是 Normal,那麼產生的效果是:

  1. 若是 props.onChange(value) 能夠在 16ms 內執行完,則與不使用 unstable_next 沒有區別。
  2. 若是 props.onChange(value) 的執行時間過長,可能這個函數會在下次幾回的 Render 中陸續執行,不會阻塞後續的高優先級任務。

調度帶來的限制

調度系統也存在兩個問題。

  1. 調度系統只能有一個,若是同時存在兩個調度系統,就沒法保證調度正確性。
  2. 調度系統能力有限,只能在瀏覽器提供的能力範圍內進行調度,而沒法影響好比 Html 的渲染、回收週期。

爲了解決這個問題,Chrome 正在與 React、Polymer、Ember、Google Maps、Web Standars Community 共同建立一個 瀏覽器調度規範,提供瀏覽器級別 API,可讓調度控制更底層的渲染時機,也保證調度器的惟一性。

3. 精讀

關於 React 調度系統的剖析,能夠讀 深刻剖析 React Concurrent 這篇文章,感謝咱們團隊的 淡蒼 提供。

簡單來講,一次 Render 通常涉及到許多子節點,而 Fiber 架構在 Render 階段能夠暫停,一個一個節點的執行,從而實現了調度的能力。

React 調度能力的限制

這意味着,若是你的 React 應用目前是流暢的,開啓 Concurrent 並不會對你的應用帶來性能體驗上的提高,若是你的 React 應用目前是卡頓的,或者在某些場景下是卡頓的,那麼 Concurrent 或許能夠挽救你一下,帶來一些改變。

正如《深刻剖析 React Concurrent》一文提到的,若是你的應用沒有性能問題,就不要期望 React 調度能力有所幫助了。

這也是在說,若是一段代碼邏輯不存在性能問題,就不須要使用 Concurrent 優化,由於這種優化是無效的。咱們須要能分辨哪些邏輯須要優化,哪些邏輯不要。

從如今開始嘗試 Function Component

爲了配合 React Schedule 的實現,學會使用 Function Component 模式編寫組件是很重要的,由於:

  1. Class Component 的生命週期概念阻礙了 React 調度系統對任務的拆分。
  2. 調度系統可能對 componentWillMount 重複調用,使得 Class Component 模式下很容易寫出錯誤的代碼。
  3. Function Component 遵循了更嚴格的反作用分離,這使得 Concurrent 執行過程不會引起意外效果。

React.lazy

與 Concurrent 一塊兒發佈的,還有 React 組件動態 import 與載入方案。正常的組件載入是這樣的:

import OtherComponent from "./OtherComponent";

function MyComponent() {
  return (
    <div> <OtherComponent /> </div>
  );
}
複製代碼

但若是使用了 import() 動態載入,可使用 React.lazy 讓動態引入的組件像普通組件同樣被使用:

const OtherComponent = React.lazy(() => import("./OtherComponent"));

function MyComponent() {
  return (
    <div> <OtherComponent /> </div>
  );
}
複製代碼

若是要加入 Loading,就能夠配合 Suspense 一塊兒使用:

import React, { lazy, Suspense } from "react";
const OtherComponent = lazy(() => import("./OtherComponent"));

function MyComponent() {
  return (
    <Suspense fallback={<div>Loading...</div>}> <OtherComponent /> </Suspense>
  );
}
複製代碼

和 Concurrent 相似,React.lazy 方案也是一種對性能有益的組件加載方案。

調度分類

調度分 4 個等級:

  • Immediate:當即執行,最高優先級。
  • render-blocking:會阻塞渲染的優先級,優先級相似 requestAnimationFrame。若是這種優先級任務不能被執行,就可能致使 UI 渲染被 block。
  • default:默認優先級,普通的優先級。優先級能夠理解爲 setTimeout(0) 的優先級。
  • idle:好比通知等任務,用戶看不到或者不在乎的。

目前建議的 API 相似以下:

function mytask() {
  ...
}

myQueue = TaskQueue.default("render-blocking")
複製代碼

先建立一個執行隊列,並設置隊列的優先級。

taskId = myQueue.postTask(myTask, <list of args>);
複製代碼

再提交隊列,拿到當前隊列的執行 id,經過這個 id 能夠判斷隊列什麼時候執行完畢。

myQueue.cancelTask(taskId);
複製代碼

必要的時候能夠取消某個函數的執行。

4. 總結

隨着 Hooks 的發佈,即將到來的 Concurrent 與 Suspense 你是否準備好了呢?

筆者但願你們一塊兒思考,這三種 API 會給前端開發帶來什麼樣的改變?歡迎留言!

討論地址是:精讀《Scheduling in React》 · Issue #146 · dt-fe/weekly

若是你想參與討論,請 點擊這裏,每週都有新的主題,週末或週一發佈。前端精讀 - 幫你篩選靠譜的內容。

關注 前端精讀微信公衆號

special Sponsors

版權聲明:自由轉載-非商用-非衍生-保持署名(創意共享 3.0 許可證

相關文章
相關標籤/搜索