Featured image of post React 並發功能:建立響應式 UIFeatured image of post React 並發功能:建立響應式 UI

React 並發功能:建立響應式 UI

掌握 React 並發功能:useTransition、useDeferredValue、Suspense、startTransition 以及使用 React 18+ 建立響應式輸入元件。

React 18 引入了並發功能,從根本上改變了渲染的工作方式。這些功能使 React 可以一次準備多個版本的 UI、中斷正在進行的工作,並優先考慮緊急更新而不是非緊急更新。結果是應用程式回應速度更快,而無需放棄使 React 高效的聲明式程式設計模型。

React 中的並發不是一種全有或全無的模式。與早期實驗版本中廢棄的「並發模式」概念不同,React 18+ 選擇加入並發功能。您可以逐一採用它們,因為它們可以提供最大的價值。底層 Fiber 架構使這成為可能:React 的渲染階段可以暫停和恢復,因此協調器可以隨著優先順序的變化在不同的工作單元之間切換。

使用轉換鉤子

useTransition 掛鉤可讓您將狀態更新標記為非緊急。這可以防止更新在關鍵用戶互動(例如打字)期間阻塞主執行緒。

import { useTransition } from "react";

function SearchPage() {
  const [isPending, startTransition] = useTransition();
  const [query, setQuery] = useState("");

  function handleChange(e) {
    // Urgent: update the input value immediately
    const value = e.target.value;
    setQuery(value);

    // Non-urgent: filter the large dataset
    startTransition(() => {
      setFilter(value);
    });
  }

  return (
    <div>
      <input value={query} onChange={handleChange} />
      {isPending && <Spinner />}
      <SearchResults filter={filter} />
    </div>
  );
}

isPending 標誌提供了一種在不阻塞 UI 的情況下顯示載入指示器的方法。當 React 正在進行轉換時,isPendingtrue,使用者可以繼續鍵入,因為轉換可能會被更緊急的狀態更新中斷。

轉換與使用 setTimeout 進行去抖動有根本的不同。轉換根本不會延遲執行。它降低了渲染工作的優先級,允許 React 暫停它,檢查更高優先級的工作,並在執行緒空閒時恢復。去抖動總是會引入固定的延遲;當 CPU 空閒時,轉換會引入零延遲。


useDeferredValue 鉤子

其中 useTransition 包裝狀態設定器, useDeferredValue 包裝值。當您無法輕鬆包裝狀態更新本身但仍希望保持 UI 回應時,這非常有用。

import { useDeferredValue, useMemo } from "react";

function SearchResults({ query, data }) {
  const deferredQuery = useDeferredValue(query);
  const isStale = query !== deferredQuery;

  const filtered = useMemo(
    () => expensiveFilter(data, deferredQuery),
    [deferredQuery, data]
  );

  return (
    <div style={{ opacity: isStale ? 0.5 : 1 }}>
      {filtered.map((item) => (
        <ListItem key={item.id} item={item} />
      ))}
    </div>
  );
}

當 CPU 閒置時,延遲值會立即更新,而不會在固定延遲之後更新。這會建立一個「陳舊狀態」模式:使用者在計算新結果時看到先前的結果(稍微變暗),而不是空白的載入旋轉器。這種模式提供了一種感覺上更流暢的體驗,因為 UI 永遠不會變空。

方法延遲使用者體驗
同步渲染每次擊鍵都充滿UI 在過濾期間凍結
去抖動(300 毫秒)固定 300 毫秒延遲輸入感覺延遲
佔位符_0空閒時零延遲反應靈敏輸入,平滑過渡

並發渲染的懸念

在並發渲染下,懸念變得更加強大。在同步模型中,當元件掛起時,Suspense 邊界上方的整個樹都會回落到載入狀態。透過並發功能,同級組件在解析掛起的組件時保持互動。

function Page() {
  return (
    <Suspense fallback={<PageSkeleton />}>
      <Sidebar />
      <Suspense fallback={<DetailSkeleton />}>
        <MainContent />
      </Suspense>
    </Suspense>
  );
}

嵌套 Suspense 邊界可讓您控制載入狀態的粒度。 Sidebar 可以在 MainContent 流入時立即渲染。與 startTransition 結合使用,您可以在載入下一個畫面時顯示上一個 UI,從而消除困擾傳統導覽的「載入閃現」。


startTransition 外部鉤子

startTransition 函數也可以作為獨立導入在 React 元件之外使用。這對於狀態管理庫、路由器事件處理程序以及任何鉤子不可用的程式碼路徑都很有價值。

import { startTransition } from "react";
import { useNavigate } from "react-router-dom";

function navigateWithTransition(path) {
  startTransition(() => {
    // Wrap the navigation in a transition
    // so the previous view stays visible
    navigate(path);
  });
}

您可以將 Zustand 或 Redux 儲存更新包裝在 startTransition 中,以降低非緊急狀態變更的優先權。當儲存更新觸發許多訂閱者之間昂貴的衍生運算時,這尤其有用。


建立響應式搜尋元件

實用的搜尋元件將所有並發功能組合在一起。輸入狀態緊急。過濾器計算使用 useDeferredValue。導覽至詳細資料頁面使用路由器上的 startTransition 。搜尋結果本身被包裹在懸念邊界。

以下是一個完整的模式:

function SearchContainer() {
  const [input, setInput] = useState("");
  const deferredInput = useDeferredValue(input);

  function handleClear() {
    setInput("");
    // The deferred value will catch up naturally
  }

  return (
    <Suspense fallback={<ResultsSkeleton />}>
      <SearchInput
        value={input}
        onChange={setInput}
        onClear={handleClear}
      />
      <SearchResults query={deferredInput} />
    </Suspense>
  );
}

當使用者快速鍵入時,只有最後一次擊鍵才會觸發完整渲染。所有中間擊鍵都會中斷先前的轉換。這避免了“卡頓搜尋”問題,即打字感覺緩慢,因為每次擊鍵都會觸發昂貴的過濾器通過。


高級模式和最佳實踐

對於分層優先權控制,請將 useTransitionuseDeferredValue 組合。使用 useTransition 進行導覽層級狀態變更(選項卡切換、頁面轉換),使用 useDeferredValue 進行視圖內的衍生計算。

若要測試並發功能,請使用帶有 act 包裝器的 React 測試庫:

import { act, render, screen } from "@testing-library/react";

await act(async () => {
  render(<SearchPage />);
});

// Assert on the pending state
expect(screen.getByText("Loading...")).toBeInTheDocument();

優化前的設定檔。 React DevTools Profiler 顯示渲染是否中斷以及每個渲染階段消耗了多少時間。過早使用並發功能會增加複雜性,而不會帶來可衡量的好處 - 始終首先進行測量,然後在解決真正瓶頸的地方應用轉換和延遲值。


相容性和遷移

並發功能需要 React 18+ 並且完全可以選擇加入。升級時不會破壞現有組件。建議的遷移路徑從高延遲使用者互動開始:搜尋輸入、過濾大型清單和選項卡切換。一旦這些順利進行,就擴展到導航轉換和資料載入模式。

一些同步改變狀態的第三方函式庫可能不尊重轉換邊界。在這些上下文中採用轉換之前測試庫相容性。

概括

並發功能為您提供了三個強大的工具:用於取消狀態更新優先級的 useTransition、用於推遲派生計算的 useDeferredValue 以及用於優雅加載狀態的並發 Suspense。每個解決特定類別的回應問題。首先進行分析,選擇正確的工具,並提供即使在繁重的計算下也感覺即時的 UI。