ReactJS - useDeferredValue 钩子



React 18 的版本包含许多新的 React 钩子,这些钩子有助于并发和渲染慢速内容。useDeferredValue 钩子是那些简单易用但难以理解的钩子之一。在本教程中,我们将了解这个钩子是如何工作的,以便我们知道如何以及何时使用它。

useDeferredValue 钩子的工作

在 React 中,useDeferredValue 钩子用于与并发模式交互。并发模式是一个实验性的 React 功能,它允许我们安排和优先处理渲染更新,以创建响应速度更快、更动态的用户界面。

useDeferredValue 通常用于在需要时延迟某些值的处理。这有助于最大限度地减少在给定渲染周期内完成的工作量,并提高应用程序的性能。当我们的值不是立即需要时,例如网络查询或大型计算,此钩子非常有用。

我们可以在组件的顶层调用“useDeferredValue”来获取值的延迟版本。

语法


const deferredValue = useDeferredValue(value);

参数

  • value − 这是我们想要推迟的值。它通常是一段数据或一个变量,不能立即需要,或者可以在以后处理。

返回值

返回的延迟值将与我们在原始渲染期间给出的值相同。在更新过程中,React 将首先尝试使用旧值重新渲染,然后在后台使用新值再次重新渲染。

我们可以通过三种方式使用此钩子。首先,通过延迟用户界面的一部分的重新呈现,其次通过显示内容已变旧,第三,通过在加载新内容时显示旧内容。

例子

因此,我们将讨论使用 useDeferredValue 钩子的这三种方法。

示例 - 推迟用户界面(UI)的一部分的重新渲染

在 React 应用程序中,我们可能会面临这样一种情况,即 UI 的一部分更新缓慢并且无法使其更快。假设我们有一个文本输入字段,每次我们写一个字母时,一个组件(例如图表或长列表)都会刷新或重新呈现。这种频繁的重新渲染可能会减慢我们的应用程序速度,即使对于像打字这样的简单操作也是如此。

可以使用 useDeferredValue 钩子来避免此缓慢的组件影响 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;

输出

慢速组件

示例 - 显示内容已过时

使用 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'); // 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;

输出

初始数据

示例 - 在加载新内容时显示旧内容

我们可以使用 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>
	 	 	 </>
	 	);
}

输出

搜索数据

局限性

在 React 中,useDeferredValue 钩子有一些限制,可以用几句话来定义 -

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