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
广告