useDeferredValue 的基础用法
useDeferredValue
可以让你延迟更新 UI 的某些部分
用法如下:
jsx
const deferredValue = useDeferredValue(someValue);
其中someValue
是你想要延迟的值,它可以是任何类型。
deferredValue
的渲染有两种情况:
- 在初始渲染时 ,
deferredValue
的值将与someValue
的值相同。 - 在UI更新期间 ,因为
deferredValue
的优先级较低,即使并发模式下deferredValue
已在后台更新,React也会先使用旧值渲染,当其它高优先级的状态更新完成,才会把deferredValue
新值渲染出来。
useDeferredValue 应用示例
在新内容加载期间显示旧内容
比如,在搜索时,我们可能会配合 Suspense
组件,当发起搜索请求时,展示 fallback
的内容,请求完毕时,再显示最新的结果。
jsx
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>
</>
);
}
像这样,有一个 loading 效果:
然而,我们可以通过 useDeferredValue
,在搜索时,延迟更新的结果 ,也就是在发送搜索请求时,不展示 loading,旧数据依然展示(下面的例子我们给旧数据置灰),当请求完毕时,再去渲染最新的数据
jsx
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>
<div style={{
opacity: isStale ? 0.5 : 1,
transition: isStale ? 'opacity 0.2s 0.2s linear' : 'opacity 0s 0s linear'
}}>
<SearchResults query={deferredQuery} />
</div>
</>
);
}
- 当搜索
a
时:
- 然后再搜索
ab
时,旧数据置灰,当请求成功时,再渲染最新的数据
延迟渲染 UI 的某些部分
你还可以将 useDeferredValue
作为性能优化的手段。当你的 UI 某个部分重新渲染很慢、没有简单的优化方法,同时你又希望避免它阻塞其他 UI 的渲染时,使用 useDeferredValue
很有帮助。
想象一下,你有一个文本框和一个组件(例如图表或长列表),在每次按键时都会重新渲染:
jsx
// app.jsx
import { useState, useDeferredValue } from 'react';
import SlowList from './SlowList.js';
export default function App() {
const [text, setText] = useState('');
return (
<>
<input value={text} onChange={e => setText(e.target.value)} />
<SlowList text={text} />
</>
);
}
每次你输入的时候,SlowList
都会接受到新的 props
,然后重新渲染,就会导致卡顿,然而,我们可以通过 useDeferredValue
去做延迟渲染
jsx
// app.jsx
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} />
</>
);
}
// SlowList.jsx
import { memo } from 'react';
const SlowList = memo(function SlowList({ text }) {
// 仅打印一次。实际的减速是在 SlowItem 组件内部。
console.log('[ARTIFICIALLY SLOW] Rendering 250 <SlowItem />');
let items = [];
for (let i = 0; i < 250; i++) {
items.push(<SlowItem key={i} text={text} />);
}
return (
<ul className="items">
{items}
</ul>
);
});
function SlowItem({ text }) {
let startTime = performance.now();
while (performance.now() - startTime < 1) {
// 每个 item 暂停 1ms,模拟极其缓慢的代码
}
return (
<li className="item">
Text: {text}
</li>
)
}
export default SlowList;
效果如下:
-
在输入停止前,不会 rerender
-
输入停止后,再渲染
useDeferredValue 和 节流、防抖的区别
从上面的学习可以看出来,useDeferredValue
在某些场景下是可以替换掉节流
和防抖
的
防抖与节流的局限性:
- 这两种方法都是为了
控制函数的执行频率
,但它们是阻塞
的,可能会导致不流畅的用户体验。
useDeferredValue
的优势:
- 它与React深度集成,可以适应用户的设备。如果设备性能好,延迟的重新渲染会很快完成;如果设备性能差,重新渲染会相应地延迟。
- 它不需要选择固定的延迟,与防抖和节流不同。
- 由
useDeferredValue
执行的重新渲染是可中断
的。这意味着在React重新渲染期间,如果发生了其他更新,React会中断当前的渲染并处理新的更新。
适用场景:
- 如果要优化的工作不是在渲染期间进行的,例如减少网络请求,那么防抖和节流仍然是有用的。
- 如果优化的目标是和渲染有关的,建议使用
useDeferredValue