Site icon May's Notes

React hook useTransition 的使用場景

react

適用場景

  1. 提高UX: 當執行耗時的狀態更新時,例如過濾大型資料或渲染大量資料時,useTransition 可以將這些更新標記為低優先級,確保用戶的互動(如輸入、點擊)保持流暢。
  2. 提高性能: 當進行狀態更新可能導致大量 DOM 變化時,例如切換頁面、加載資料等,useTransition 可以讓瀏覽器有更多時間處理高優先級的操作。
  3. 分離緊急與非緊急更新: 高優先級更新(如輸入框內容變更)與低優先級更新(如篩選數據顯示結果)可以使用 useTransition 分開處理,避免阻塞。

場景一:搜尋過濾

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

function App() {
  const [query, setQuery] = useState(''); // 高優先級
  const [filteredItems, setFilteredItems] = useState([]); // 低優先級
  const [isPending, startTransition] = useTransition();

  const items = Array.from({ length: 10000 }, (_, i) => `Item ${i + 1}`);

  const handleInputChange = (event) => {
    const value = event.target.value;
    setQuery(value); // 即時更新輸入框(高優先級)

    startTransition(() => {
      const result = items.filter((item) =>
        item.toLowerCase().includes(value.toLowerCase())
      );
      setFilteredItems(result); // 篩選操作(低優先級)
    });
  };

  return (
    <div>
      <input
        type="text"
        value={query}
        onChange={handleInputChange}
        placeholder="Search..."
      />
      {isPending && <p>Loading...</p>}
      <ul>
        {filteredItems.map((item, index) => (
          <li key={index}>{item}</li>
        ))}
      </ul>
    </div>
  );
}

export default App;

場景二:切換頁面

假設有一個按鈕可以切換不同的list頁面,每次切換都會渲染大量資料。使用 useTransition 可以讓按鈕的狀態改變(如顯示 “Loading…”)先於資料渲染,提升UX。

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

function App() {
  const [currentPage, setCurrentPage] = useState(1);
  const [data, setData] = useState([]);
  const [isPending, startTransition] = useTransition();

  const fetchData = (page) => {
    return Array.from({ length: 5000 }, (_, i) => `Page ${page} - Item ${i + 1}`);
  };

  const handlePageChange = (page) => {
    startTransition(() => {
      setCurrentPage(page);
      const newData = fetchData(page);
      setData(newData); // 更新大數據渲染為低優先級
    });
  };

  return (
    <div>
      <div>
        <button onClick={() => handlePageChange(1)} disabled={isPending}>
          Page 1
        </button>
        <button onClick={() => handlePageChange(2)} disabled={isPending}>
          Page 2
        </button>
        {isPending && <p>Loading data...</p>}
      </div>
      <ul>
        {data.map((item, index) => (
          <li key={index}>{item}</li>
        ))}
      </ul>
    </div>
  );
}

export default App;

場景三:UI 更新

在動態切換顏色或樣式的情境下,UI 更新可能會導致卡頓。可以用 useTransition 將樣式變化標記為低優先級。

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

function App() {
  const [color, setColor] = useState("red");
  const [boxes, setBoxes] = useState([]);
  const [isPending, startTransition] = useTransition();

  const changeColor = (newColor) => {
    startTransition(() => {
      setColor(newColor); // 延遲樣式更新
      const newBoxes = Array.from({ length: 10000 }, (_, i) => ({
        id: i,
        color: newColor,
      }));
      setBoxes(newBoxes);
    });
  };

  return (
    <div>
      <button onClick={() => changeColor("red")} disabled={isPending}>
        Red
      </button>
      <button onClick={() => changeColor("blue")} disabled={isPending}>
        Blue
      </button>
      <div style={{ display: "flex", flexWrap: "wrap" }}>
        {boxes.map((box) => (
          <div
            key={box.id}
            style={{
              width: 10,
              height: 10,
              backgroundColor: box.color,
              margin: 1,
            }}
          />
        ))}
      </div>
      {isPending && <p>Updating...</p>}
    </div>
  );
}

export default App;

場景四:滾動加載

透過 useTransition,可以在用戶滾動時,逐步載入未顯示的分頁。

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

function App() {
  const [currentPage, setCurrentPage] = useState(0); // 當前分頁
  const [visibleData, setVisibleData] = useState([]); // 已載入的資料
  const [isPending, startTransition] = useTransition();

  const pageSize = 100; // 每頁顯示的數量
  const totalData = Array.from({ length: 10000 }, (_, i) => `Item ${i + 1}`);

  useEffect(() => {
    setVisibleData(totalData.slice(0, pageSize));
  }, []);

  const loadMoreData = () => {
    if (currentPage * pageSize >= totalData.length) return;

    startTransition(() => {
      const nextPage = currentPage + 1;
      const nextData = totalData.slice(0, nextPage * pageSize);
      setVisibleData(nextData);
      setCurrentPage(nextPage);
    });
  };

  return (
    <div>
      <div
        style={{ height: '300px', overflow: 'auto', border: '1px solid black' }}
        onScroll={(e) => {
          const { scrollTop, scrollHeight, clientHeight } = e.target;
          if (scrollTop + clientHeight >= scrollHeight - 20) {
            loadMoreData(); // 滾動到底部時載入更多
          }
        }}
      >
        <ul>
          {visibleData.map((item, index) => (
            <li key={index}>{item}</li>
          ))}
        </ul>
      </div>
      {isPending && <p>Loading more data...</p>}
    </div>
  );
}

export default App;

場景五:動態排序和動畫過渡

對大型列表進行排序時,會觸發大量 DOM 更新,可能導致頁面卡頓。透過 useTransition,可以在排序過程中加入動畫過渡,顯得更流暢。

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

function App() {
  const [items, setItems] = useState(
    Array.from({ length: 50 }, (_, i) => ({ id: i, value: Math.random() }))
  );
  const [isPending, startTransition] = useTransition();

  const sortItems = () => {
    startTransition(() => {
      const sorted = [...items].sort((a, b) => a.value - b.value); // 排序資料
      setItems(sorted); // 更新為低優先級操作
    });
  };

  return (
    <div>
      <button onClick={sortItems} disabled={isPending}>
        Sort
      </button>
      <div style={{ display: 'grid', gridTemplateColumns: 'repeat(5, 1fr)', gap: '10px' }}>
        {items.map((item) => (
          <div
            key={item.id}
            style={{
              height: '50px',
              background: 'skyblue',
              transition: 'transform 0.3s ease',
              transform: `translateY(${Math.random() * 10}px)`
            }}
          >
            {item.value.toFixed(2)}
          </div>
        ))}
      </div>
      {isPending && <p>Sorting...</p>}
    </div>
  );
}

export default App;

場景六:主題切換

在應用程式中切換主題時,涉及大量的樣式更新,可能會導致整個頁面閃爍。useTransition 可以確保主題切換過程更加流暢。

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

function App() {
  const [theme, setTheme] = useState('light');
  const [isPending, startTransition] = useTransition();

  const toggleTheme = () => {
    startTransition(() => {
      setTheme((prev) => (prev === 'light' ? 'dark' : 'light'));
    });
  };

  return (
    <div
      style={{
        background: theme === 'light' ? '#fff' : '#333',
        color: theme === 'light' ? '#000' : '#fff',
        minHeight: '100vh',
        transition: 'all 0.5s ease'
      }}
    >
      <button onClick={toggleTheme} disabled={isPending}>
        Toggle Theme
      </button>
      <p>Current theme: {theme}</p>
    </div>
  );
}

export default App;

小結

useTransition 的主要作用是將耗時操作標記為低優先級,從而避免用戶界面卡頓。

使用 useTransition 時,關鍵是區分哪些是高優先級的緊急操作(例如用戶輸入、點擊)和低優先級的非緊急操作(例如渲染或數據更新)。

Exit mobile version