S
Shih, Cheng-Fu6 個月前

關於useTransition


理解 useTransition 在 React 中的應用

useTransition 用來幫助管理可能阻塞 UI 的狀態更新。允許將某些更新標記為“過渡”,這些更新可以被更緊急的更新所打斷。這可以通過使 UI 更加響應來改善用戶體驗,即使在處理緩慢或複雜的狀態更新時。

useTransition 的簡介

useTransition Hook 返回一個包含兩個元素的陣列:

  1. isPending:一個布林值,表示當前是否有過渡正在進行中。
  2. startTransition:一個函數,用來包裝應被視為過渡的狀態更新。
const [isPending, startTransition] = useTransition();

useTransition 的基本用法和例子

當你希望在不阻塞 UI 的情況下更新狀態時,可以將狀態更新包裝在 startTransition 函數內。以下是一個基本例子:

import React, { useState, useTransition } from 'react';

function TabContainer() {
  const [isPending, startTransition] = useTransition();
  const [tab, setTab] = useState('home');

  function selectTab(nextTab) {
    startTransition(() => {
      setTab(nextTab);
    });
  }

  return (
    <div>
      <button onClick={() => selectTab('home')}>Home</button>
      <button onClick={() => selectTab('profile')}>Profile</button>
      <div>{isPending ? 'Loading...' : `Content for ${tab}`}</div>
    </div>
  );
}

將狀態更新標記為過渡

通過將狀態更新標記為過渡,來確保更緊急的更新(如用戶輸入)優先處理。這有助於保持 UI 的響應性。

在這個例子中,輸入框中的輸入操作不會被狀態更新所阻塞。相反,狀態更新將被視為過渡來處理,確保輸入保持響應。

import React, { useState, useTransition } from 'react';

function App() {
  const [isPending, startTransition] = useTransition();
  const [value, setValue] = useState('');

  const handleChange = (event) => {
    startTransition(() => {
      setValue(event.target.value);
    });
  };

  return (
    <div>
      <input type="text" onChange={handleChange} value={value} />
      {isPending && <p>Updating...</p>}
    </div>
  );
}

在過渡中更新父元件的狀態

你也可以使用 useTransition 來更新父元件的狀態。以下是一個例子:

import React, { useState, useTransition } from 'react';

function ParentComponent() {
  const [isPending, startTransition] = useTransition();
  const [data, setData] = useState(null);

  const fetchData = () => {
    startTransition(() => {
      // 模擬數據獲取
      setTimeout(() => {
        setData({ message: 'Data loaded' });
      }, 2000);
    });
  };

  return (
    <div>
      <button onClick={fetchData}>Load Data</button>
      {isPending ? <p>Loading...</p> : <p>{data?.message}</p>}
    </div>
  );
}

在過渡期間顯示錯誤

要在過渡期間顯示錯誤,可以使用錯誤邊界。React 允許你在渲染階段捕捉錯誤,這可以使用錯誤邊界來實現。

import React, { useState, useTransition } from 'react';

function ErrorBoundary({ children }) {
  return (
    <React.Suspense fallback={<div>Loading...</div>}>
      {children}
    </React.Suspense>
  );
}

function App() {
  const [isPending, startTransition] = useTransition();
  const [data, setData] = useState(null);
  const [error, setError] = useState(null);

  const fetchData = () => {
    startTransition(() => {
      // 模擬數據獲取
      setTimeout(() => {
        try {
          // 模擬一個錯誤
          throw new Error('Failed to fetch data');
        } catch (err) {
          setError(err);
        }
      }, 2000);
    });
  };

  return (
    <ErrorBoundary>
      <button onClick={fetchData}>Fetch Data</button>
      {isPending && <p>Loading...</p>}
      {error ? <p style={{ color: 'red' }}>{error.message}</p> : <p>{data}</p>}
    </ErrorBoundary>
  );
}

在過渡期間顯示待處理的視覺狀態

你可以使用 useTransition 返回的 isPending 布林值來向用戶表明當前處於過渡中。例如,選項卡按鈕可以有一個特殊的“待處理”視覺狀態:

function TabButton({ children, isActive, onClick }) {
  const [isPending, startTransition] = useTransition();
  // ...
  if (isPending) {
    return <b className="pending">{children}</b>;
  }
  // ...
}

避免不必要的加載指示器

在這個例子中,PostsTab 組件從啟用了 Suspense 的數據源中獲取數據。當你點擊“Posts”選項卡時,PostsTab 組件將掛起,導致最近的加載中的後備方案出現:

import { Suspense, useState } from 'react';
import TabButton from './TabButton.js';
import AboutTab from './AboutTab.js';
import PostsTab from './PostsTab.js';
import ContactTab from './ContactTab.js';

export default function TabContainer() {
  const [tab, setTab] = useState('about');
  return (
    <Suspense fallback={<h1>🌀 Loading...</h1>}>
      <TabButton
        isActive={tab === 'about'}
        onClick={() => setTab('about')}
      >
        About
      </TabButton>
      <TabButton
        isActive={tab === 'posts'}
        onClick={() => setTab('posts')}
      >
        Posts
      </TabButton>
      <TabButton
        isActive={tab === 'contact'}
        onClick={() => setTab('contact')}
      >
        Contact
      </TabButton>
      <hr />
      {tab === 'about' && <AboutTab />}
      {tab === 'posts' && <PostsTab />}
      {tab === 'contact' && <ContactTab />}
    </Suspense>
  );
}

隱藏整個選項卡容器以顯示加載指示器會導致用戶體驗不連貫。如果你將 useTransition 添加到 TabButton 中,你可以改為在選項卡按鈕中指示待處理狀態。

import { useTransition } from 'react';

export default function TabButton({ children, isActive, onClick }) {
  const [isPending, startTransition] = useTransition();
  if (isActive) {
    return <b>{children}</b>
  }
  if (isPending) {
    return <b className="pending">{children}</b>;
  }
  return (
    <button onClick={() => {
      startTransition(() => {
        onClick();
      });
    }}>
      {children}
    </button>
  );
}

過渡效果只會“等待”足夠長的時間來避免隱藏已經顯示的內容(例如選項卡容器)。如果“Posts”選項卡具有一個嵌套 Suspense 邊界,過渡效果將不會“等待”它。

常見問題

無法更新輸入框內容

避免使用 startTransition 來更新受控的輸入框。輸入框的狀態更新應保持同步,以確保立即反饋。

import React, { useState, useTransition } from 'react';

function App() {
  const [text, setText] = useState('');
  const [isPending, startTransition] = useTransition();

  const handleChange = (e) => {
    setText(e.target.value); // 同步更新以確保立即反饋
    startTransition(() => {
      // 任何其他可延遲的狀態更新
    });
  };

  return <input value={text} onChange={handleChange} />;
}

過渡未被識別

確保狀態更新被包裝在 startTransition 函數調用內,且這些更新必須是同步

的。

startTransition(() => {
  setState(newState);
});

總結

有效使用 useTransition 可以通過以非阻塞的方式管理狀態更新,大大提升 React 應用程式的性能和響應性。通過將不太緊急的更新標記為過渡,你可以確保用戶界面更加順暢和響應。


其他關於hook系列文章:

關於useDeferredValue 關於useImperativeHandle 關於useSyncExternalStore 關於useTransition


圖片
圖片
圖片
圖片
圖片
圖片
(使用 Facebook 留言外掛程式 留言無法滿足本網站參加活動之資格,僅供非會員討論使用)
互動地圖
interactive taiwan map