banner
AgedCoffee

AgedCoffee

Suspense的基本原理

原文地址:A Fundamental Guide To React Suspense

另一个将在 React 18 中发布的大功能是 Suspense。如果你在 React 开发领域呆的时间比较长,那么你会知道 Suspense 功能并不是特别新。早在 2018 年,Suspense 是作为 React 16.6 版本的一部分,作为一个实验性功能发布的。然后,它主要是针对处理与 React.lazy 相结合的代码分割。

但現在,隨著 React 18 的發布,Suspense 的正式發布擺在了我們面前。與並發渲染的發布一起,Suspense 的真正力量終於被釋放出來。Suspense 和並發渲染之間的互動為改善用戶體驗開闢了一個巨大的機會世界。

但是,就像所有的功能一樣,就像並發的渲染一樣,從基本原理開始是很重要的。到底什麼是 Suspense?為什麼我們一開始就需要 Suspense?Suspense 是如何解決這個問題的?有什麼好處?為了幫助你理解這些基本原理,本文將確切地討論這些問題,並為你提供有關 Suspense 主題的堅實知識基礎。

什麼是 Suspense#

從本質上講,Suspense 是一種機制,讓 React 開發者向 React 表明,一個組件正在等待數據準備就緒。然後 React 知道它應該等待該數據被取走。同時,將向用戶顯示一個回退,React 將繼續渲染應用程序的其餘部分。在數據準備好後,React 將回到那個特定的用戶界面,並相應地更新它。

從根本上說,這聽起來與目前 React 開發者必須實現數據獲取流程的方式沒有太大區別:使用某種狀態來指示組件是否仍在等待數據,一個開始獲取數據的 useEffect,根據數據的狀態顯示一個加載狀態,並在數據準備好後更新 UI。

但在實踐中,Suspense 在技術上使這種情況發生,是完全不同的。與提到的數據獲取流程相反,Suspense 與 React 深度整合,允許開發者更直觀地協調加載狀態,並避免了競賽條件。為了更好地理解這些細節,重要的是要知道我們為什麼需要 Suspense。

我們為什麼需要 Suspense#

在沒有 Suspense 的情況下,有兩種主要的方法來實現數據獲取流:render 時獲取和 fetch-then-render。然而,這些傳統的數據獲取流程存在一些問題。為了理解 Suspense,我們必須深入研究這些流程的問題和限制。

Fetch-on-render#

大多數人都會像之前提到的那樣,使用 useEffect 和狀態變量來實現數據的獲取流程。這意味著只有當一個組件渲染時才開始獲取數據。所有的數據獲取都發生在組件的效果和生命週期方法中。

這種方法的主要問題是,組件只在渲染時觸發數據獲取,其異步性質迫使組件必須等待其他組件的數據請求。

假設我們有一個組件 ComponentA,它獲取了一些數據並有一個加載狀態。在內部,ComponentA 還渲染了另一個組件 ComponentB,它自己也執行了一些數據獲取。但是由於數據獲取的實現方式,ComponentB 只有在被渲染後才開始獲取數據。這意味著它必須等待,直到 ComponentA 完成數據的獲取,然後再渲染 ComponentB。

這導致了一種瀑布式的方法,即組件之間的數據獲取是按順序進行的,這基本上意味著它們是相互阻塞的。

function ComponentA() {
  const [data, setData] = useState(null)

  useEffect(() => {
    fetchAwesomeData().then((data) => setData(data))
  }, [])

  if (user === null) {
    return <p>Loading data...</p>
  }

  return (
    <>
      <h1>{data.title}</h1>
      <ComponentB />
    </>
  )
}

function ComponentB() {
  const [data, setData] = useState(null)

  useEffect(() => {
    fetchGreatData().then((data) => setData(data))
  }, [])

  return data === null ? <h2>Loading data...</h2> : <SomeComponent data={data} />
}

Fetch-then-render#

為了防止組件之間的數據獲取的順序阻塞,一個替代方案是儘可能早地開始所有的數據獲取工作。因此,與其讓組件負責處理渲染時的數據獲取和數據請求分別發生,不如在樹開始渲染之前啟動所有的請求。

這種方法的好處是,所有的數據請求都是一起發起的,因此組件 B 不必等待組件 A 完成。這就解決了組件依次阻斷對方數據流的問題。然而,這也帶來了另一個問題,那就是我們必須等待所有的數據請求完成後才能為用戶呈現任何東西。可以想象,這不是一個最佳的體驗。

// 在渲染整個樹之前開始獲取數據
function fetchAllData() {
  return Promise.all([fetchAwesomeData(), fetchGreatData()]).then(([awesomeData, greatData]) => ({
    awesomeData,
    greatData,
  }))
}

const promise = fetchAllData()

function ComponentA() {
  const [awesomeData, setAwesomeData] = useState(null)
  const [greatData, setGreatData] = useState(null)

  useEffect(() => {
    promise.then(({ awesomeData, greatData }) => {
      setAwesomeData(awesomeData)
      setGreatData(greatData)
    })
  }, [])

  if (user === null) {
    return <p>Loading data...</p>
  }

  return (
    <>
      <h1>{data.title}</h1>
      <ComponentB />
    </>
  )
}

function ComponentB({ data }) {
  return data === null ? <h2>Loading data...</h2> : <SomeComponent data={data} />
}

Suspense 是怎麼解決這個問題的#

從本質上講,fetch-on-render 和 fetch-then-render 的主要問題歸結為一個事實,即我們試圖強行同步兩個不同的流程,即數據獲取流程和 React 生命週期。通過 Suspense,我們得到了一種不同的數據獲取方法,即所謂的邊獲取邊渲染的方法。

const specialSuspenseResource = fetchAllDataSuspense()

function App() {
  return (
    <Suspense fallback={<h1>Loading data...</h1>}>
      <ComponentA />
      <Suspense fallback={<h2>Loading data...</h2>}>
        <ComponentB />
      </Suspense>
    </Suspense>
  )
}

function ComponentA() {
  const data = specialSuspenseResource.awesomeData.read()
  return <h1>{data.title}</h1>
}

function ComponentB() {
  const data = specialSuspenseResource.greatData.read()
  return <SomeComponent data={data} />
}

與之前的實現不同的是,它允許組件在 React 到達的那一刻啟動數據獲取。這甚至發生在組件渲染之前,而且 React 並沒有就此停止。然後,它繼續評估組件的子樹,並繼續嘗試渲染它,同時等待數據獲取的完成。

這意味著 Suspense 不會阻塞渲染,這意味著子組件不必等待父組件完成後再啟動它們的數據獲取請求。React 試圖儘可能地渲染,同時啟動適當的數據獲取請求。在一個請求完成後,React 將重新訪問相應的組件,並使用新收到的數據相應地更新用戶界面。

Suspense 的好處是什麼#

Suspense 有很多好處,特別是在用戶體驗方面。但其中一些好處也涵蓋了開發者的體驗。

  • 儘早啟動獲取。Suspense 引入的 render-as-you-fetch 方法的最大和最直接的好處是,數據獲取被儘可能早地啟動。這意味著用戶需要等待的時間更短,應用程序的速度更快,這對任何前端應用程序來說都是普遍有益的。

  • 更直觀的加載狀態。有了 Suspense,組件不必再包括一大堆混亂的 if 語句,也不必再單獨跟蹤狀態來實現加載狀態。相反,加載狀態被集成到它所屬的組件本身。這使得組件更加直觀,因為它將加載代碼與相關代碼保持在一起,並且由於加載狀態被包含在組件中,因此更容易重複使用。

  • 避免了競賽條件。現有的數據獲取實現的一個問題,我在這篇文章中沒有深入介紹,就是競賽條件。在某些情況下,傳統的 "渲染時獲取" 和 "渲染時獲取" 的實現方式可能會導致競賽條件,這取決於不同的因素,如時間、用戶輸入和參數化數據請求。主要的潛在問題是,我們試圖強行同步兩個不同的進程,即 React 的和數據獲取的進程。但有了 Suspense,這就可以更優雅地進行整合,從而避免了上述問題。

  • 更加綜合的錯誤處理。使用 Suspense,我們基本上已經為數據請求流創建了邊界。除此之外,由於 Suspense 使其與組件的代碼整合得更直觀,它允許 React 開發人員也為 React 代碼和數據請求實現更多的集成錯誤處理。

最後總結#

React Suspense 已經被關注了 3 年多了。但隨著 React 18 的發布,正式發布的時間越來越近了。繼並發渲染之後,它將是作為這個 React 版本的一部分而發布的最大功能之一。就其本身而言,它可以將數據獲取和加載狀態的實現提升到一個新的直觀性和優雅的水平。

為了幫助你了解 Suspense 的基本原理,這篇文章涵蓋了對它很重要的幾個問題和方面。這涉及到了什麼是 Suspense,為什麼我們首先需要 Suspense 這樣的東西,它是如何解決某些數據獲取問題的,以及 Suspense 帶來的所有好處。

載入中......
此文章數據所有權由區塊鏈加密技術和智能合約保障僅歸創作者所有。