ReactJS - useDeferredValue 鉤子



React 18 版本包含許多新的 React hook,有助於併發和渲染緩慢的內容。useDeferredValue hook 就是其中一個易於使用但難以理解的 hook。在本教程中,我們將瞭解此 hook 的工作原理,以便我們知道如何以及何時使用它。

useDeferredValue hook 的工作原理

在 React 中,useDeferredValue hook 用於與併發模式互動。併發模式是 React 的一個實驗性功能,允許我們排程和優先處理渲染更新,以建立更具響應性和動態性的使用者介面。

useDeferredValue 通常用於推遲處理某些值的時機。這有助於最大限度地減少給定渲染週期中完成的工作量,並提高應用程式的效能。當我們擁有不需要立即處理的值時,例如網路查詢或大型計算,此 hook 非常有用。

我們可以在元件的頂層呼叫“useDeferredValue”以獲取值的延遲版本。

語法

const deferredValue = useDeferredValue(value);

引數

value − 這是我們想要推遲的值。它通常是一段資料或一個變數,可能不需要立即使用或可以在稍後處理。

返回值

返回的延遲值將與我們在原始渲染期間提供的值相同。在更新期間,React 將首先嚐試使用舊值重新渲染,然後在後臺使用新值進行另一次重新渲染。

我們可以透過三種方式使用此 hook。首先是推遲使用者介面一部分的重新渲染,其次是顯示內容已過期,第三是顯示舊內容,同時載入新內容。

示例

因此,我們將討論使用 useDeferredValue hook 的這三種方式。

示例 - 推遲使用者介面 (UI) 部分的重新渲染

在 React 應用程式中,我們可能會遇到 UI 的一部分更新緩慢且無法使其更快的情況。假設我們有一個文字輸入欄位,並且每次我們寫入一個字母時,一個元件(例如圖表或長列表)都會重新整理或重新渲染。這種頻繁的重新渲染可能會減慢我們的應用程式速度,即使對於像打字這樣的簡單操作也是如此。

可以使用 useDeferredValue hook 來避免此緩慢的元件影響 UI 的其餘部分。

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

function SlowList({ text }) {
   
   // Slow operation like rendering a long list
   const slowListRendering = (text) => {
      console.log(`Rendering the list for text: ${text}`);
      setTimeout(() => {
         console.log('List rendering complete.');
      }, 1000);
   };
   
   slowListRendering(text);
   
   return (
      <div>
         <p>This is a slow component.</p>
         {/* Render a chart here */}
      </div>
   );
}

function App() {
   const [text, setText] = useState('');
   const deferredText = useDeferredValue(text); // Defer the text input value
   
   return (
      <>
         <input value={text} onChange={(e) => setText(e.target.value)} />
         <SlowList text={deferredText} /> {/* Pass the deferred value */}
      </>
   );
}
export default App;

輸出

slow component

示例 - 顯示內容已過期

使用 useDeferredValue 時,需要顯示內容已過期,以便告知使用者由於延遲,資料可能不是最新的。

在下面的示例中,isStale 變數是透過比較延遲資料與當前資料來計算的。如果它們不匹配,則表示材料已過期,UI 會顯示“正在更新...”訊息,以告知使用者資料正在更新。使用 useDeferredValue 時,這是提供有關資料陳舊性反饋的簡便方法。

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

function MyComponent({ deferredData }) {
   
   // Slow operation
   function slowOperation(data) {
      return new Promise((resolve) => {
         setTimeout(() => {
            resolve(`Processed data: ${data}`);
         }, 2000);
      });
   }
   
   const [data, setData] = useState('Initial data'); // Initialize data
   const isStale = deferredData !== data;
   
   if (isStale) {
   
      // Data is old, simulate loading
      slowOperation(deferredData).then((result) => {
         setData(result); // Update data when it's no longer old
      });
   }
   
   return (
      <div>
         <p>Data: {data}</p>
         {isStale && <p>Updating...</p>}
      </div>
   );
}

function App() {
   const [inputData, setInputData] = useState('');
   const deferredInputData = useDeferredValue(inputData);
   
   return (
      <div>
         <input
         type="text"
         value={inputData}
         onChange={(e) => setInputData(e.target.value)}
         />
         <MyComponent deferredData={deferredInputData} />
      </div>
   );
}
export default App;

輸出

initial data

示例 - 在載入新內容時顯示舊內容

我們可以使用 useDeferredValue 保留舊資料和新資料狀態,並根據資料的陳舊性有條件地渲染它們,以便在載入新內容時顯示舊內容。以下是一個示例:

data.js

let cache = new Map();

export function fetchData(url) {
   if (!cache.has(url)) {
      cache.set(url, getData(url));
   }
   return cache.get(url);
}

async function getData(url) {
   if (url.startsWith("/search?q=")) {
      return await getSearchResults(url.slice("/search?q=".length));
   } else {
      throw Error("Not Found");
   }
}

async function getSearchResults(query) {

   // Delay to make waiting
   await new Promise((resolve) => {
      setTimeout(resolve, 400);
   });
   
   const allData = [
      {
         id: 1,
         title: "ABC",
         year: 2000
      },
      {
         id: 2,
         title: "DEF",
         year: 2001
      },
      {
         id: 3,
         title: "GHI",
         year: 2002
      },
      {
         id: 4,
         title: "JKL",
         year: 2003
      },
      {
         id: 5,
         title: "MNO",
         year: 2004
      },
      {
         id: 6,
         title: "PQR",
         year: 2005
      },
      {
         id: 7,
         title: "STU",
         year: 2006
      },
      {
         id: 8,
         title: "VWX",
         year: 2007
      },
      {
         id: 9,
         title: "YZ",
         year: 2008
      }
   ];
   
   const lowerQuery = query.trim().toLowerCase();
   return allData.filter((data) => {
      const lowerTitle = data.title.toLowerCase();
      return (
         lowerTitle.startsWith(lowerQuery) ||
         lowerTitle.indexOf(" " + lowerQuery) !== -1
      );
   });
}

SearchData.js

import { fetchData } from "./data.js";

export default function SearchData({ query }) {
   if (query === "") {
      return null;
   }
   const myData = use(fetchData(`/search?q=${query}`));
   if (myData.length === 0) {
      return (
         <p>
            No data found for <i>"{query}"</i>
         </p>
      );
   }
   return (
      <ul>
         {myData.map((data) => (
            <li key={data.id}>
            {data.title} ({data.year})
            </li>
         ))}
      </ul>
   );
}

function use(promise) {
   if (promise.status === "fulfilled") {
      return promise.value;
   } else if (promise.status === "rejected") {
      throw promise.reason;
   } else if (promise.status ===    "pending") {
      throw promise;
   } else {
      promise.status = "pending";
      promise.then(
         (result) => {
            promise.status = "fulfilled";
            promise.value = result;
         },
         (reason) => {
            promise.status = "rejected";
            promise.reason = reason;
         }
      );
      throw promise;
   }
}

App.js

import { Suspense, useState } from 'react';
import SearchData from './SearchData.js';

export default function App() {
   const [query, setQuery] = useState('');
   return (
      <>
         <label>
            Search for the Data here:
            <input value={query} onChange={e => setQuery(e.target.value)} />
         </label>
         <Suspense fallback={<h2>Data is Loading...</h2>}>
            <SearchData query={query} />
         </Suspense>
      </>
   );
}

輸出

search for data

限制

在 React 中,useDeferredValue hook 有一些限制,可以用幾句話來概括:

  • 一些較舊的網路瀏覽器可能無法完全支援 useDeferredValue hook。因此,如果我們需要支援過時的瀏覽器,我們可能會遇到困難。

  • 處理多個延遲值時,使用 useDeferredValue 可能會使我們的程式碼複雜化。這種額外的複雜性會使我們的程式碼更難以理解和維護。

  • 雖然 useDeferredValue 可以幫助提高效能,但它並不是所有效能問題的最佳解決方案。我們仍然需要考慮其他效能最佳化,例如程式碼分割和伺服器端渲染,以獲得更好的結果。

  • 如果開發人員不熟悉 React,則可能需要付出努力和一些時間才能理解 useDeferredValue hook。

reactjs_reference_api.htm
廣告