Suspense對React的意義在哪裡?

卡頌發表於2022-02-08

大家好,我卡頌。

可能很多朋友在專案中還沒用過Suspense,但是SuspenseReact未來發展非常重要的一環。

本文會講解Suspense對於React的意義。

歡迎加入人類高質量前端框架群,帶飛

React的迭代過程

React從v16到v18主打的特性經歷了三次大的變化:

  • v16:Async Mode(非同步模式)
  • v17:Concurrent Mode(併發模式)
  • v18:Concurrent Render(併發更新)

要了解這三次變化的意義,需要先了解React中一個很容易混淆的概念 —— render(渲染)。

ClassComponentrender函式執行時被稱為render

class App extends Component {
  render() {
    // ...這是render函式
  }
}

而將render的結果渲染到頁面的過程,被稱為commit

Async Mode的目的是為了讓render變為非同步、可中斷的。

Concurrent Mode的目的是讓commit在使用者的感知上是併發的。

由於Concurrent Mode包含breaking change,所以v18提出了Concurrent Render,減少開發者遷移的成本。

那麼讓commit在使用者的感知上是併發的是什麼意思呢?

“併發”的意義

說到併發,就不得不提Suspense。考慮如下程式碼:

const App = () => {
  const [count, setCount] = useState(0);
  
  useEffect(() => {
    setInterval(() => {
      setCount(count => count + 1);
    }, 1000);
  }, []);
  
  return (
    <>
      <Suspense fallback={<div>loading...</div>}>
        <Sub count={count} />
      </Suspense>
      <div>count is {count}</div>
    </>
  );
};

其中:

  • 每過一秒會觸發一次更新,將狀態count更新為count => count + 1
  • Sub中會發起非同步請求,請求返回前,包裹SubSuspense會渲染fallback

假設請求三秒後返回,理想情況下,請求發起前後頁面會依次顯示為:

// Sub內請求發起前
<div class=“sub”>I am sub, count is 0</div>
<div>count is 0</div>

// Sub內請求發起第1秒
<div>loading...</div>
<div>count is 1</div>

// Sub內請求發起第2秒
<div>loading...</div>
<div>count is 2</div>

// Sub內請求發起第3秒
<div>loading...</div>
<div>count is 3</div>

// Sub內請求成功後
<div class=“sub”>I am sub, request success, count is 4</div>
<div>count is 4</div>

從使用者的視角觀察,頁面中有兩個任務在併發執行:

  1. 請求Sub的任務(觀察第一個div的變化)
  2. 改變count的任務(觀察第二個div的變化)

Suspense帶來的頁面中多工併發執行感覺,就是Concurrent(併發)在React中的含義。

其實在Async Mode時,已經支援Suspense。但是上面的程式碼在Async Mode的頁面中表現如下:

// Sub內請求發起前
<div class=“sub”>I am sub, count is 0</div>
<div>count is 0</div>

// Sub內請求發起第1秒
<div>loading...</div>
<div>count is 0</div>

// Sub內請求發起第2秒
<div>loading...</div>
<div>count is 0</div>

// Sub內請求發起第3秒
<div>loading...</div>
<div>count is 0</div>

// Sub內請求成功後
<div class=“sub”>I am sub, request success, count is 4</div>
<div>count is 4</div>

從使用者的視角觀察,當請求Sub的任務執行時,改變count的任務就被凍結了。

這就是為什麼被稱為Async(非同步)而不是Concurrent(併發)。

Suspense的意義

可以看到,對於ConcurrentSuspense是必不可少的一環。

可以認為,Suspense的作用是劃分頁面中需要併發渲染的部分

比如上例中,通過Suspense請求Sub的任務改變count的任務劃分開,從視覺上併發執行。

當明確了Suspense的意義後,你會發現,React接下來在做的事,就是不斷擴充Suspense的場景(也就是說將更多場景納入併發渲染的範疇)。

比如,當前已有的:

  • React.lazy
  • 通過React提供的fetch庫改造後的非同步請求
  • useTransition
  • useDeferredvalue

未來會加入的:

  • Server Component
  • Selective Hydration

總結

React的發展歷程是:從同步非同步,再到併發

當實現併發後,接下來的發展方向將是:不斷擴充套件可以使用併發的場景。

Suspense的作用是劃分頁面中需要併發渲染的部分

這套發展路徑從React誕生伊始就決定了,因為從架構上來說,React重度依賴執行時,為了優化效能,併發是這套架構下的最優發展方向。

相關文章