useDeferredValue
是一个 React 钩子,可让你推迟更新 UI 的一部分。
¥useDeferredValue
is a React Hook that lets you defer updating a part of the UI.
const deferredValue = useDeferredValue(value)
参考
¥Reference
useDeferredValue(value, initialValue?)
在组件的顶层调用 useDeferredValue
以获得该值的延迟版本。
¥Call useDeferredValue
at the top level of your component to get a deferred version of that value.
import { useState, useDeferredValue } from 'react';
function SearchPage() {
const [query, setQuery] = useState('');
const deferredQuery = useDeferredValue(query);
// ...
}
参数
¥Parameters
-
value
:你要延迟的值。它可以有任何类型。¥
value
: The value you want to defer. It can have any type.
可选 initialValue
:在组件初始渲染期间使用的值。如果省略此选项,useDeferredValue
将不会在初始渲染期间推迟,因为没有可以替代渲染的 value
的先前版本。
¥optional initialValue
: A value to use during the initial render of a component. If this option is omitted, useDeferredValue
will not defer during the initial render, because there’s no previous version of value
that it can render instead.
返回
¥Returns
-
currentValue
:在初始渲染期间,返回的延迟值将与你提供的值相同。在更新期间,React 将首先尝试使用旧值重新渲染(因此它将返回旧值),然后在后台尝试使用新值进行另一次重新渲染(因此它将返回更新后的值)。¥
currentValue
: During the initial render, the returned deferred value will be the same as the value you provided. During updates, React will first attempt a re-render with the old value (so it will return the old value), and then try another re-render in the background with the new value (so it will return the updated value).
注意事项
¥Caveats
-
当更新在 Transition 中时,
useDeferredValue
始终返回新的value
并且不会产生延迟渲染,因为更新已被延迟。¥When an update is inside a Transition,
useDeferredValue
always returns the newvalue
and does not spawn a deferred render, since the update is already deferred. -
你传递给
useDeferredValue
的值应该是原始值(如字符串和数字)或在渲染之外创建的对象。如果你在渲染期间创建一个新对象并立即将其传递给useDeferredValue
,则每次渲染时都会有所不同,从而导致不必要的背景重新渲染。¥The values you pass to
useDeferredValue
should either be primitive values (like strings and numbers) or objects created outside of rendering. If you create a new object during rendering and immediately pass it touseDeferredValue
, it will be different on every render, causing unnecessary background re-renders. -
当
useDeferredValue
收到不同的值(与Object.is
相比)时,除了当前渲染(当它仍然使用以前的值时)之外,它还会安排在后台使用新值重新渲染。后台重新渲染是可中断的:如果value
有另一个更新,React 将从头开始重新启动后台重新渲染。例如,如果用户键入输入的速度快于接收延迟值的图表可以重新渲染的速度,则图表只会在用户停止输入后重新渲染。¥When
useDeferredValue
receives a different value (compared withObject.is
), in addition to the current render (when it still uses the previous value), it schedules a re-render in the background with the new value. The background re-render is interruptible: if there’s another update to thevalue
, React will restart the background re-render from scratch. For example, if the user is typing into an input faster than a chart receiving its deferred value can re-render, the chart will only re-render after the user stops typing. -
useDeferredValue
与<Suspense>
。 集成 如果新值引起的后台更新暂停了 UI,用户将看不到回退。在数据加载之前,它们将看到旧的延迟值。¥
useDeferredValue
is integrated with<Suspense>
. If the background update caused by a new value suspends the UI, the user will not see the fallback. They will see the old deferred value until the data loads. -
useDeferredValue
本身并不能阻止额外的网络请求。¥
useDeferredValue
does not by itself prevent extra network requests. -
useDeferredValue
本身没有固定延迟。一旦 React 完成原始重新渲染,React 将立即开始使用新的延迟值进行后台重新渲染。由事件(如类型)引起的任何更新都会中断后台重新渲染并优先于它。¥There is no fixed delay caused by
useDeferredValue
itself. As soon as React finishes the original re-render, React will immediately start working on the background re-render with the new deferred value. Any updates caused by events (like typing) will interrupt the background re-render and get prioritized over it. -
由
useDeferredValue
引起的背景重新渲染在提交到屏幕之前不会触发副作用。如果后台重新渲染挂起,其副作用将在数据加载和 UI 更新后运行。¥The background re-render caused by
useDeferredValue
does not fire Effects until it’s committed to the screen. If the background re-render suspends, its Effects will run after the data loads and the UI updates.
用法
¥Usage
在加载新内容时显示旧内容
¥Showing stale content while fresh content is loading
在组件的顶层调用 useDeferredValue
以延迟更新 UI 的某些部分。
¥Call useDeferredValue
at the top level of your component to defer updating some part of your UI.
import { useState, useDeferredValue } from 'react';
function SearchPage() {
const [query, setQuery] = useState('');
const deferredQuery = useDeferredValue(query);
// ...
}
在初始渲染期间,延迟值 将与你提供的值 相同。
¥During the initial render, the deferred value will be the same as the value you provided.
在更新期间,延迟值 将 “滞后” 最新的 值。特别是,React 将首先重新渲染而不更新延迟值,然后尝试在后台使用新接收到的值重新渲染。
¥During updates, the deferred value will “lag behind” the latest value. In particular, React will first re-render without updating the deferred value, and then try to re-render with the newly received value in the background.
让我们通过一个例子来看看这在什么时候有用。
¥Let’s walk through an example to see when this is useful.
在此示例中,SearchResults
组件 suspends 同时获取搜索结果。尝试键入 "a"
,等待结果,然后将其编辑为 "ab"
。"a"
的结果被加载回退替换。
¥In this example, the SearchResults
component suspends while fetching the search results. Try typing "a"
, waiting for the results, and then editing it to "ab"
. The results for "a"
get replaced by the loading fallback.
import { Suspense, useState } from 'react'; import SearchResults from './SearchResults.js'; export default function App() { const [query, setQuery] = useState(''); return ( <> <label> Search albums: <input value={query} onChange={e => setQuery(e.target.value)} /> </label> <Suspense fallback={<h2>Loading...</h2>}> <SearchResults query={query} /> </Suspense> </> ); }
一种常见的替代 UI 模式是推迟更新结果列表并继续显示以前的结果,直到新结果准备就绪。调用 useDeferredValue
向下传递查询的延迟版本:
¥A common alternative UI pattern is to defer updating the list of results and to keep showing the previous results until the new results are ready. Call useDeferredValue
to pass a deferred version of the query down:
export default function App() {
const [query, setQuery] = useState('');
const deferredQuery = useDeferredValue(query);
return (
<>
<label>
Search albums:
<input value={query} onChange={e => setQuery(e.target.value)} />
</label>
<Suspense fallback={<h2>Loading...</h2>}>
<SearchResults query={deferredQuery} />
</Suspense>
</>
);
}
query
将立即更新,因此输入将显示新值。但是,deferredQuery
将保留其先前的值,直到数据加载完毕,因此 SearchResults
将显示旧的结果。
¥The query
will update immediately, so the input will display the new value. However, the deferredQuery
will keep its previous value until the data has loaded, so SearchResults
will show the stale results for a bit.
在下例中输入 "a"
,等待结果加载,然后编辑输入为 "ab"
。请注意,在加载新结果之前,你现在看到的不是 Suspense 回退,而是旧的结果列表:
¥Enter "a"
in the example below, wait for the results to load, and then edit the input to "ab"
. Notice how instead of the Suspense fallback, you now see the stale result list until the new results have loaded:
import { Suspense, useState, useDeferredValue } from 'react'; import SearchResults from './SearchResults.js'; export default function App() { const [query, setQuery] = useState(''); const deferredQuery = useDeferredValue(query); return ( <> <label> Search albums: <input value={query} onChange={e => setQuery(e.target.value)} /> </label> <Suspense fallback={<h2>Loading...</h2>}> <SearchResults query={deferredQuery} /> </Suspense> </> ); }
深入研究
¥How does deferring a value work under the hood?
你可以认为它分两步发生:
¥You can think of it as happening in two steps:
-
首先,React 使用新的
query
("ab"
) 重新渲染,但使用旧的deferredQuery
(仍然是"a")
。你传递给结果列表的deferredQuery
值被延迟:它 “滞后”query
值。¥First, React re-renders with the new
query
("ab"
) but with the olddeferredQuery
(still"a")
. ThedeferredQuery
value, which you pass to the result list, is deferred: it “lags behind” thequery
value. -
在后台,React 尝试重新渲染,并将
query
和deferredQuery
更新为"ab"
。如果重新渲染完成,React 会将其显示在屏幕上。但是,如果它挂起("ab"
的结果尚未加载),React 将放弃此渲染尝试,并在数据加载后再次尝试重新渲染。在数据准备好之前,用户将一直看到旧的延迟值。¥In the background, React tries to re-render with both
query
anddeferredQuery
updated to"ab"
. If this re-render completes, React will show it on the screen. However, if it suspends (the results for"ab"
have not loaded yet), React will abandon this rendering attempt, and retry this re-render again after the data has loaded. The user will keep seeing the stale deferred value until the data is ready.
延迟的 “background” 渲染是可中断的。例如,如果你再次键入输入,React 将放弃它并使用新值重新启动。React 将始终使用最新提供的值。
¥The deferred “background” rendering is interruptible. For example, if you type into the input again, React will abandon it and restart with the new value. React will always use the latest provided value.
请注意,每次击键仍然有一个网络请求。这里延迟的是显示结果(直到它们准备好),而不是网络请求本身。即使用户继续键入,每次击键的响应都会被缓存,因此按下退格键是即时的,不会再次获取。
¥Note that there is still a network request per each keystroke. What’s being deferred here is displaying results (until they’re ready), not the network requests themselves. Even if the user continues typing, responses for each keystroke get cached, so pressing Backspace is instant and doesn’t fetch again.
表明内容是旧的
¥Indicating that the content is stale
在上面的示例中,没有迹象表明最新查询的结果列表仍在加载。如果新结果需要一段时间才能加载,这可能会让用户感到困惑。为了让用户更清楚地知道结果列表与最新的查询不匹配,你可以在显示旧的结果列表时添加一个视觉指示:
¥In the example above, there is no indication that the result list for the latest query is still loading. This can be confusing to the user if the new results take a while to load. To make it more obvious to the user that the result list does not match the latest query, you can add a visual indication when the stale result list is displayed:
<div style={{
opacity: query !== deferredQuery ? 0.5 : 1,
}}>
<SearchResults query={deferredQuery} />
</div>
通过此更改,一旦你开始键入,旧的结果列表就会稍微变暗,直到加载新的结果列表。你还可以添加 CSS 转场来延迟变暗,使其感觉渐变,如下例所示:
¥With this change, as soon as you start typing, the stale result list gets slightly dimmed until the new result list loads. You can also add a CSS transition to delay dimming so that it feels gradual, like in the example below:
import { Suspense, useState, useDeferredValue } from 'react'; import SearchResults from './SearchResults.js'; export default function App() { const [query, setQuery] = useState(''); const deferredQuery = useDeferredValue(query); const isStale = query !== deferredQuery; return ( <> <label> Search albums: <input value={query} onChange={e => setQuery(e.target.value)} /> </label> <Suspense fallback={<h2>Loading...</h2>}> <div style={{ opacity: isStale ? 0.5 : 1, transition: isStale ? 'opacity 0.2s 0.2s linear' : 'opacity 0s 0s linear' }}> <SearchResults query={deferredQuery} /> </div> </Suspense> </> ); }
延迟部分 UI 的重新渲染
¥Deferring re-rendering for a part of the UI
你还可以应用 useDeferredValue
作为性能优化。当你的 UI 的一部分重新渲染很慢,没有简单的方法来优化它,并且你想防止它阻塞 UI 的其余部分时,它很有用。
¥You can also apply useDeferredValue
as a performance optimization. It is useful when a part of your UI is slow to re-render, there’s no easy way to optimize it, and you want to prevent it from blocking the rest of the UI.
假设你有一个文本字段和一个组件(如图表或长列表),它们在每次击键时重新渲染:
¥Imagine you have a text field and a component (like a chart or a long list) that re-renders on every keystroke:
function App() {
const [text, setText] = useState('');
return (
<>
<input value={text} onChange={e => setText(e.target.value)} />
<SlowList text={text} />
</>
);
}
首先,优化 SlowList
以在其属性相同时跳过重新渲染。为此,将其封装在 memo
中:
¥First, optimize SlowList
to skip re-rendering when its props are the same. To do this, wrap it in memo
:
const SlowList = memo(function SlowList({ text }) {
// ...
});
但是,这仅在 SlowList
属性与之前渲染期间相同时才有用。你现在面临的问题是当它们不同时速度很慢,并且当你实际需要显示不同的视觉输出时。
¥However, this only helps if the SlowList
props are the same as during the previous render. The problem you’re facing now is that it’s slow when they’re different, and when you actually need to show different visual output.
具体来说,主要的性能问题是每当你键入输入时,SlowList
都会收到新的属性,重新渲染它的整个树会让输入感觉很卡顿。在这种情况下,useDeferredValue
允许你优先更新输入(必须快)而不是更新结果列表(允许更慢):
¥Concretely, the main performance problem is that whenever you type into the input, the SlowList
receives new props, and re-rendering its entire tree makes the typing feel janky. In this case, useDeferredValue
lets you prioritize updating the input (which must be fast) over updating the result list (which is allowed to be slower):
function App() {
const [text, setText] = useState('');
const deferredText = useDeferredValue(text);
return (
<>
<input value={text} onChange={e => setText(e.target.value)} />
<SlowList text={deferredText} />
</>
);
}
这不会使 SlowList
的重新渲染更快。但是,它告诉 React 可以取消重新渲染列表的优先级,这样它就不会阻塞击键。该列表将 “滞后” 输入,然后是 “赶上”。和以前一样,React 会尝试尽快更新列表,但不会阻止用户输入。
¥This does not make re-rendering of the SlowList
faster. However, it tells React that re-rendering the list can be deprioritized so that it doesn’t block the keystrokes. The list will “lag behind” the input and then “catch up”. Like before, React will attempt to update the list as soon as possible, but will not block the user from typing.
例子 1 / 2: 延迟重新渲染列表
¥Deferred re-rendering of the list
在此示例中,SlowList
组件中的每个项目都被人为减慢,以便你可以看到 useDeferredValue
如何让你保持输入响应。键入输入并注意在列表 “滞后” 时键入感觉很活泼。
¥In this example, each item in the SlowList
component is artificially slowed down so that you can see how useDeferredValue
lets you keep the input responsive. Type into the input and notice that typing feels snappy while the list “lags behind” it.
import { useState, useDeferredValue } from 'react'; import SlowList from './SlowList.js'; export default function App() { const [text, setText] = useState(''); const deferredText = useDeferredValue(text); return ( <> <input value={text} onChange={e => setText(e.target.value)} /> <SlowList text={deferredText} /> </> ); }
深入研究
¥How is deferring a value different from debouncing and throttling?
在这种情况下,你之前可能使用过两种常见的优化技术:
¥There are two common optimization techniques you might have used before in this scenario:
-
去抖动意味着你会在更新列表之前等待用户停止输入(例如一秒钟)。
¥Debouncing means you’d wait for the user to stop typing (e.g. for a second) before updating the list.
-
节流意味着你会每隔一段时间更新一次列表(例如最多每秒一次)。
¥Throttling means you’d update the list every once in a while (e.g. at most once a second).
虽然这些技术在某些情况下很有用,但 useDeferredValue
更适合优化渲染,因为它与 React 本身深度集成并适应用户的设备。
¥While these techniques are helpful in some cases, useDeferredValue
is better suited to optimizing rendering because it is deeply integrated with React itself and adapts to the user’s device.
与去抖或节流不同,它不需要选择任何固定延迟。如果用户的设备速度很快(例如功能强大的注意本电脑),则延迟重新渲染几乎会立即发生并且不会引起注意。如果用户的设备速度很慢,则列表会将输入 “滞后” 与设备的速度成正比。
¥Unlike debouncing or throttling, it doesn’t require choosing any fixed delay. If the user’s device is fast (e.g. powerful laptop), the deferred re-render would happen almost immediately and wouldn’t be noticeable. If the user’s device is slow, the list would “lag behind” the input proportionally to how slow the device is.
此外,与去抖动或节流不同,useDeferredValue
完成的延迟重新渲染在默认情况下是可中断的。这意味着如果 React 正在重新渲染一个大列表,但用户再次进行击键,React 将放弃该重新渲染,处理击键,然后再次在后台开始渲染。相比之下,去抖动和节流仍然会产生卡顿体验,因为它们会阻塞:它们只是推迟渲染阻止击键的时刻。
¥Also, unlike with debouncing or throttling, deferred re-renders done by useDeferredValue
are interruptible by default. This means that if React is in the middle of re-rendering a large list, but the user makes another keystroke, React will abandon that re-render, handle the keystroke, and then start rendering in the background again. By contrast, debouncing and throttling still produce a janky experience because they’re blocking: they merely postpone the moment when rendering blocks the keystroke.
如果你正在优化的工作在渲染期间没有发生,去抖动和节流仍然有用。例如,它们可以让你触发更少的网络请求。你也可以结合使用这些技术。
¥If the work you’re optimizing doesn’t happen during rendering, debouncing and throttling are still useful. For example, they can let you fire fewer network requests. You can also use these techniques together.