iT邦幫忙

2021 iThome 鐵人賽

DAY 29
0
Modern Web

用30天更加認識 React.js 這個好朋友系列 第 29

Day29-淺談 React Concurrent Mode & 相關功能(Fiber、Suspense、useTransition、useDeferredValue)

這篇會介紹 React 的一個新模式 Concurrent Mode 以及關於它的一些功能。

Concurrent Mode

Concurrent Mode 是什麼?

它由數個新的 React 功能組成,不過這些功能都還在實驗階段。

目前 React 有三種模式,由圖可以看到原本的 React 程式碼變成了 Legacy Mode,另外還有夾在兩個模式過渡的 Blocking Mode。在 Concurrent Mode 的部分,新增了幾個新的功能/特性,本文將會介紹標題提到的那幾項功能。

圖片來源

使用 Concurrent Mode

如果想要先試用這個新功能的話,需先安裝:

npm install react@experimental react-dom@experimental

原本在根元件的程式也要修改:

import ReactDOM from 'react-dom';
const root = document.getElementById('root');

// 舊
ReactDOM.render(<App />, root);

// 新
ReactDOM.createRoot(root).render(<App />);

參考資料

REACT CONCURRENT MODE

Concurrent Mode API Reference (Experimental)

Fiber

複習 Reconciliation

在瞭解 Fiber 這個東西前,先簡單複習一下 Reconciliation。

在 React 中因為狀態的改變而要重新渲染時,一連串決定哪些 DOM 元素要做更新的過程,就是 Reconciliation。包含以下的步驟:

  1. React 讀取在網頁上的 DOM Tree 並複製一份變成 Virtual DOM 儲存起來
  2. 當網頁上某元件的 DOM 有任何改變時,會產生新的一組 Virtual DOM
  3. 透過 Diffing 演算法,在記憶體內部算出要更新的 Virtual DOM
  4. 最後在網頁上的 Real DOM 只會更新需要更新的 DOM

Fiber 的由來

一個功能的產生想必是為了解決某個問題,Fiber 也是如此,所以先瞭解一下在 Fiber 出現前的 React 有什麼問題?

在 React V15 中的 React,使用的是 Stack reconciler 去比對更新前後的 DOM Tree 哪裡不一樣。

當進行 Reconciliation 時,比對兩個 Virtual DOM Tree 不同的部分是透過從 DOM Tree 的根節點不斷做遞迴將整個 DOM Tree,並搭配上 call stack(堆疊)的方式和另一個 DOM Tree 做比對,以找出不同之處。

若 Tree 的結構較為複雜的話,運作這些步驟將會很花時間,來不及在一次 frame 時間內處理完時就會掉幀。

Fiber 便是為了解決效能和渲染時的流程度而生。

Fiber 功用

在 React V16 中,Fiber reconciler 取代了 Stack reconciler,這個東西可以根據整個要渲染的 DOM Tree 創造出多個小節點,將整個渲染的任務拆分成許多的小任務,也就是這次要介紹的主角: Fiber。

由 Fiber 構成的渲染任務可以被中斷、重用、捨棄,並且不同的渲染任務可以被設定優先權做渲染。

而單獨的一個 Fiber 有點類似 React element 的概念,會對應到 Virtual DOM 的元素,並且 Fiber 也能儲存一些元件的資料和狀態(props、state、updateQueue...),關於 Fiber 相關的屬性可以參考以下 React 的原始碼:

FiberNode

很多個 Fiber 會構成一個 Fiber Tree,其資料結構為 Linked List,以下是簡單的範例:

另外推薦一個網頁,呈現了用 Stack reconciler 和 Fiber reconciler 兩個方式頻繁改變 Sierpinski triangle 內容的差異,可以看到 Stack reconciler 的方式會造成明顯的卡頓,Fiber reconciler 則不會。

Fiber vs Stack Demo

Fiber 和渲染階段

Day23 的文章我有提到在 React 渲染時,會分成 Render Phase & Commit Phase,在這兩個階段時,Fiber 也會進行一些事情。

Render Phase

在此階段 Fiber reconciler 會建立 Fiber,產生 Fiber Tree,,若有需要更新的 DOM,會再建立出一個新的 Fiber Tree,稱為 workInProgress Tree。

Commit Phase

在此階段,會為 Fiber 執行在它們上面記錄的 side effect,並且在此階段運行時不可以中斷。

當 workInProgress Tree 完成更新時,會變成新的 Fiber Tree,等下次更新時又會出現新的 orkInProgress Tree,如此循環下去,這樣的過程稱為 Double Buffering。

Fiber 對生命週期的影響

在 React V16 中,廢棄了 componentWillMount、componentWillUpdate、componentWillReceiveProps,這點在 Day22 有稍微提到,而這些生命週期函式被廢棄就和 Fiber 的出現有所關聯。

由於這些函式都出現在 Render 階段,在前面說到渲染任務可以被中斷、重用、捨棄,並且不同的渲染任務可以被設定優先權做渲染,所以這些廢棄的生命週期函式都可能被重複執行,

參考資料 & 推薦閱讀

以下的影片和文章都解說的蠻詳細的,若有興趣更加了解的讀者可以進一步閱讀。

What Is React Fiber?

React Fiber 淺談

fiber reconciler 漫谈

(譯)深入瞭解React Fiber的內部


useTransition

常和 Suspense 搭配使用,它可以延遲渲染指定的元件,讓優先度較低或是比較要耗時的元件稍後渲染。

另外如果 loading 狀態過短,使用者可能在網頁上看到載入圖示只有一瞬間的時間,那這樣其實也不太需要 Loading 元件,此時透過 useTransition 延時也可以避免不必要的 Loading 元件呈現在網頁。

語法

const [startTransition, isPending] = useTransition({ timeoutMs: 指定的時間 });

  • useTransition 接收一個物件當作參數,用來設定延遲的時間
  • 回傳了名為 startTransition 的 callback 函式,
  • 回傳判斷 transition 狀態是否完成的 boolean 值: isPending。

使用

程式碼來自官網範例,在這段程式碼中,點擊按鈕時會去取得 profile 的資料,但因為將設定取回資料的 setResource 放在了 startTransition 裡面,所以會延遲更新 state,而在更新 state 前,會先出現 isPending 條件式的內容。

function App() {
  const [resource, setResource] = useState(initialResource);
  const [startTransition, isPending] = useTransition({ timeoutMs: 2000 });
  return (
    <>
      <button
        disabled={isPending}
        onClick={() => {
          startTransition(() => {
            const nextUserId = getNextId(resource.userId);
            setResource(fetchProfileData(nextUserId));
          });
        }}
      >
        Next
      </button>
      {isPending ? " Loading..." : null}
      <Suspense fallback={<Spinner />}>
        <ProfilePage resource={resource} />
      </Suspense>
    </>
  );
}

useDeferredValue

語法

const deferredValue = useDeferredValue(value, { timeoutMs: 指定的時間 });

  • 第一個參數是設定要延遲的 value 值
  • 第二個參數是延遲的秒數
  • 最後返回一個延遲的值 deferredValue

使用

程式碼來自官網範例,input 輸入框的值會隨著使用者打字不斷快速改變,但 MySlowList 會等待兩秒後才會進行更新。

function App() {
  const [text, setText] = useState("hello");
  const deferredText = useDeferredValue(text, { timeoutMs: 2000 }); 

  return (
    <div className="App">
      {/* Keep passing the current text to the input */}
      <input value={text} onChange={handleChange} />
      ...
      {/* But the list is allowed to "lag behind" when necessary */}
      <MySlowList text={deferredText} />
    </div>
  );
 }

Suspense

Suspense 在之前的 Day24 時就有介紹過,不過在 Concurrent Mode 出現之前,就有這個元件了,但只有動態載入的功能,而在 Concurrent Mode 中,增加了可以加入 Loading 狀態的元件,資料持續載入的期間就先暫時渲染 Loading 元件。


終於只剩最後一天的鐵人賽!明天會整理一些不錯的 youtube react 學習資源清單給讀者做更多的延伸學習和參考~


上一篇
Day28-介紹 Redux DevTools
下一篇
Day30-還想學更多嗎?推薦 Youtube 上面免費的 React 學習資源
系列文
用30天更加認識 React.js 這個好朋友32

尚未有邦友留言

立即登入留言