ReactJS - useDeferredValue Hook
React 18 版本包含许多新的 React hooks,它们有助于并发和渲染缓慢的内容。 useDeferredValue hook 是那些使用简单但难以理解的 hooks 之一。在本教程中,我们将了解此 hook 的工作原理,以便我们知道如何以及何时使用它。
useDeferredValue hook 的工作原理
在 React 中,useDeferredValue hook 用于与并发模式交互。并发模式是一项实验性的 React 功能,它允许我们安排和优先处理渲染更新,以创建响应更快、更动态的用户界面。
useDeferredValue 通常用于在需要某些值时推迟处理它们。这有助于最大限度地减少给定渲染周期内完成的工作量并提高应用程序的性能。当我们拥有一些不立即需要的值(例如网络查询或大型计算)时,此Hooks(钩子)非常有用。
我们可以在组件的顶层调用"useDeferredValue"来获取值的延迟版本。
语法
const deferredValue = useDeferredValue(value);
参数
value − 它是我们想要延迟的值。它通常是一段数据或一个变量,不能立即需要,或者可以稍后处理。
返回值
返回的延迟值将与我们在原始渲染期间给出的值相同。在更新期间,React 将首先尝试使用旧值重新渲染,然后使用新值在后台再次重新渲染。
我们可以通过三种方式使用此Hooks(钩子)。首先,推迟重新渲染部分用户界面,其次,显示内容已变旧,第三,在加载新内容时显示旧内容。
示例
因此,我们将讨论使用 useDeferredValue Hooks(钩子)的所有这三种方式。
示例 − 推迟重新渲染部分用户界面 (UI)
在 React 应用程序中,我们可能会遇到这样一种情况,即我们的 UI 的一部分更新缓慢且无法加快速度。假设我们有一个文本输入字段,每次我们写一个字母时,组件(例如图表或长列表)都会刷新或重新渲染。这种频繁的重新渲染可能会减慢我们的应用速度,即使是像输入这样的简单操作也是如此。
可以使用 useDeferredValue Hooks(钩子)来避免这个缓慢的组件影响 UI 的其余部分。
import React, { useState } from 'react'; import { useDeferredValue } from 'react'; function SlowList({ text }) { // 操作缓慢,例如渲染长列表 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); // 延迟文本输入值 return ( <> <input value={text} onChange={(e) => setText(e.target.value)} /> <SlowList text={deferredText} /> {/* Pass the deferred value */} </> ); } export default App;
输出

示例 − 显示内容已过时
使用 useDeferredValue 时,需要显示内容已过时,以告知用户由于延迟,数据无法保持最新状态。
在下面的示例中,isStale 变量是通过将 deferredData 与当前数据进行比较来计算的。如果它们不匹配,则表示材料已过时,并且 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'); // 初始化数据 const isStale = deferredData !== data; if (isStale) { // 数据较旧,模拟加载 slowOperation(deferredData).then((result) => { setData(result); // 当数据不再过时时更新数据 }); } 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;
输出

示例 − 在加载新内容时显示旧内容
我们可以使用 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) { // 延迟等待 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> </> ); }
输出

限制
在 React 中,useDeferredValue Hooks(钩子)有一些限制,可以用几个词来定义 −
一些较旧的 Web 浏览器可能不完全支持 useDeferredValue Hooks(钩子)。因此,如果我们需要支持过时的浏览器,我们可能会遇到困难。
在处理多个延迟值时,使用 useDeferredValue 可能会使我们的代码复杂化。这种额外的复杂性会使我们的代码更难理解和维护。
虽然 useDeferredValue 可以帮助提高性能,但它并不是所有性能问题的最佳解决方案。为了获得更好的结果,我们仍然需要考虑其他性能优化,如代码拆分和服务端渲染。
如果开发人员是 React 新手,可能需要花费一些时间和精力来理解 useDeferredValue Hooks(钩子)。