React是一个流行的用于构建用户界面的JavaScript库,它不断发展以为开发人员提供优化性能的工具。
React 18中引入的此类工具之一是useDeferredValue
钩子,它旨在通过优先渲染更新来提高应用程序的性能。
useDeferredValue
钩子是React性能优化工具集中相对较新的补充。
它在处理异步数据获取(如网络请求或从API加载数据)时特别有用。
useDeferredValue
的主要目的是,在立即呈现最重要的部分的同时,推迟对您的应用程序中不太关键的部分的更新。
这可以通过避免用户界面组件呈现延迟而大大提高应用程序的感知性能。
useDeferredValue
钩子的基本用法涉及包装状态值并创建其延迟版本。下面是一个简单的示例:
import { useState, useDeferredValue } from 'react';
function MyComponent() {
const [data, setData] = useState([]);
const deferredData = useDeferredValue(data);
// ...
}
在这个例子中,我们有一个状态变量data
,它可能会用异步操作填充数据。
通过使用useDeferredValue
,我们创建了deferredData
,这是data
的一个版本,React 会单独优先渲染。
这种分离可确保您的 UI 中的关键部分及时更新,而非关键更新(如渲染列表)可以推迟以降低性能影响。
首次渲染组件时,延迟值将与您传递的值相同。
更新组件时,延迟值将落后于最新值。这意味着 React 会首先使用旧的延迟值重新渲染组件,然后尝试在后台用新延迟值重新渲染它。
下面是一个例子,说明这在什么情况下有用:
想象一下,您有一个搜索栏,当您输入时会获取搜索结果。您开始输入“a”,React 使用加载后备项渲染搜索栏。“a”的搜索结果最终返回,React 使用结果重新渲染搜索栏。
// app.js
import { Suspense, useState } from 'react';
import SearchResults from './SearchResults.js';
export default function App() {
const [query, setQuery] = useState('');
return (
<>
<label>
Search songs:
<input value={query} onChange={e => setQuery(e.target.value)} />
</label>
<Suspense fallback={<h2>Loading...</h2>}>
<SearchResults query={query} />
</Suspense>
</>
);
}
// searchResult.js
import { fetchData } from './data.js';
export default function SearchResults({ query }) {
if (query === '') {
return null;
}
const songs = use(fetchData(`/search?q=${query}`));
if (songs.length === 0) {
return <p>No matches for <i>"{query}"</i></p>;
}
return (
<ul>
{songs.map(song => (
<li key={song.id}>
{song.title} ({song.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;
}
}
// 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 implemented');
}
}
async function getSearchResults(query) {
// Add a fake delay to make waiting noticeable.
await new Promise(resolve => {
setTimeout(resolve, 500);
});
const allSongs = [{
id: 1,
title: "Bohemian Rhapsody",
year: 1975
},
{
id: 2,
title: "Imagine",
year: 1971
},
{
id: 3,
title: "Hotel California",
year: 1976
},
{
id: 4,
title: "Stairway to Heaven",
year: 1971
},
{
id: 5,
title: "Let It Be",
year: 1970
},
{
id: 6,
title: "Abbey",
year: 1976
},
{
id: 7,
title: "A Hard Day's Night",
year: 2012
}];
const lowerQuery = query.trim().toLowerCase();
return allSongs.filter(album => {
const lowerTitle = album.title.toLowerCase();
return (
lowerTitle.startsWith(lowerQuery) ||
lowerTitle.indexOf(' ' + lowerQuery) !== -1
)
});
}
一种常用的替代UI模式涉及推迟更新结果列表并在新结果可用之前持久显示先前结果。
要实现这种方法,可以利用 useDeferredValue
钩子提供查询的延迟版本,因为它在组件层次结构中传播。
// app.js
import { Suspense, useState, useDeferredValue } from 'react';
import SearchResults from './searchResult.js';
export default function App() {
const [query, setQuery] = useState('');
const deferredQuery = useDeferredValue(query);
return (
<>
<label>
Search Songs:
<input value={query} onChange={e => setQuery(e.target.value)} />
</label>
<Suspense fallback={<h2>Loading...</h2>}>
<SearchResults query={deferredQuery} />
</Suspense>
</>
);
}
现在,您将搜索查询编辑为“ab”。React 会首先使用旧的延迟值重新渲染搜索栏,即“a”的搜索结果。 然后,它会尝试使用新的延迟值重新渲染搜索栏,即“ab”的搜索结果。
这意味着用户不会再看到加载后备,即使“ab”的搜索结果需要很长时间才能返回。
换句话说,延迟值允许您立即渲染UI,即使您正在等待异步数据。 这可以通过避免不必要的加载后备来帮助提高用户体验。
性能提升: 组件或数据的延迟加载和渲染可以导致更快的初始页面加载、降低资源使用和优化性能,从而带来更好的用户体验。
高效的资源利用: 通过按需加载所需内容,您可以减少不必要的 API 调用、最大限度地减少内存和 CPU 使用,并优化应用程序的资源利用。
渐进式加载和错误隔离: 延迟值策略支持渐进式加载以实现更高的响应性 UI,并提供更好的错误隔离,确保应用程序的一部分错误不会破坏整个用户体验。