Reference
react@18.2.0
Hook
useDeferredValue
简介
Parameters
value: 你想要推迟的值,它可以是任意类型
Returns
在首次渲染中,它返回与参数一致的值。当更新时,即由于 value(可以引起重新渲染的值) 引起重新渲染时,React 首先会尝试使用这个旧值来参与这次重新渲染,接着在后台执行 value 参与的重新渲染过程,一旦这个过程结束,就会替代掉旧值参与的结果,这时返回值就是新值了。
与 <Suspense> 打配合
请查看 <Suspense> 部分
使用 useDeferredValue 优化一部分 UI
使用 deferredValue 还可以优化一些场景。假如一个组件渲染时很慢 ,当 value 更新时引起重新渲染,这个有使用 value ,所以它也会被重新渲染,这样其他部分就会等待它渲染结束,从而产生卡顿。比如 value 就是输入框的值,那么每输入一次,value 就会更新,然而想要等待慢组件渲染,所以输入一次就会卡一次。
但是当慢组件使用 deferredValue 时,就相当于告诉 React 没必要让 value 引发的重新渲染与慢组件的重新渲染一起执行,就是说其他部分重新渲染时涉及到 value 的地方是新值,而慢组件是旧值,慢组件的使用新值的重新渲染在后台慢慢执行,当它完成时就拿上来。那其他部分就不会等待慢组件使用新值了,输入也就不卡了。
Components
<Suspense>
props
children 你真正想要渲染的 UI,如 children 在渲染的过程中出现了延迟,则 Suspense 的范围内将会展示 fallback 的内容
fallback 当 children 发送延迟时就会渲染 fallback 的内容,fallback 的内容通常是轻量的。放 children 加载完成时就会切换回 children。
- Ralay Next.js 这种可以激活 Suspense 的框架的数据请求
- 使用 lazy 懒加载的组件
一般的延迟,比如 fetch API 的延迟,解决方案就是使用 useState 控制 jsx
嵌套使用 Suspense
Suspense 会等待 children 内容全部加载完毕
那如果 component A 和 component B 都需要加载,并且已知 A 大概率快于 B,需求是 A 加载好了就先渲染,怎么办?
让 A 在 B 的上层即可,这样 Biography 加载好了就先渲染并且展示 AlbumsGlimmer
export default function ArtistPage({ artist }) {
return (
<>
<h1>{artist.name}</h1>
<Suspense fallback={<BigSpinner />}>
<Biography artistId={artist.id} />
<Suspense fallback={<AlbumsGlimmer />}>
<Panel>
<Albums artistId={artist.id} />
</Panel>
</Suspense>
</Suspense>
</>
);
}
等待加载时展示旧内容 | 与 useDeferredValue 打配合
等待加载时给予用户响应,一种方案是展示 Loading,而另一种方案是继续展示旧内容,但是样式有些许变化,比如透明度降低
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 }}>
<SearchResults query={deferredQuery} />
</div>
</Suspense>
</>
);
}
query 更新时,引起重新渲染,先是使用旧值的重新渲染,isStale 为 true,而使用新值的渲染在后台执行,执行完后替换掉旧值渲染的结果。旧值的渲染结果替换了 Suspense 的 fallback。
防止隐藏已经显示的内容 | 与 startTransition useTransition 打配合
简述下面的代码,<Suspense fallback=Loading><A/><B/></Suspense> ,因为B组件中的state更新所以B组件需要重新渲染,这会触发 Suspense,从而 loading() 替换 <A/><B/> ,这就造成了把已经显示的内容隐藏了,就A组件被替换掉了。
-
代码
// App.js import { Suspense, useState } from 'react'; import IndexPage from './IndexPage.js'; import ArtistPage from './ArtistPage.js'; import Layout from './Layout.js'; export default function App() { return ( <Suspense fallback={<BigSpinner />}> <Router /> </Suspense> ); } function Router() { const [page, setPage] = useState('/'); function navigate(url) { setPage(url); } let content; if (page === '/') { content = ( <IndexPage navigate={navigate} /> ); } else if (page === '/the-beatles') { content = ( <ArtistPage artist={{ id: 'the-beatles', name: 'The Beatles', }} /> ); } return ( <Layout> {content} </Layout> ); } function BigSpinner() { return <h2>🌀 Loading...</h2>; } // Layout.js export default function Layout({ children }) { return ( <div className="layout"> <section className="header"> Music Browser </section> <main> {children} </main> </div> ); } // IndexPage.js export default function IndexPage({ navigate }) { return ( <button onClick={() => navigate('/the-beatles')}> Open The Beatles artist page </button> ); } // ArtistPage.js export default function IndexPage({ navigate }) { return ( <button onClick={() => navigate('/the-beatles')}> Open The Beatles artist page </button> ); }
因为 startTransition API 能使 state 正常更新且不阻塞 UI,所以只需要用 startTransition 包裹住 state 的更新即可,这样 Suspense 就不会检测到B组件在加载了,因为B组件的 state 更新进入了过渡状态
function navigate(url) {
startTransition(() => {
setPage(url);
});
}
那我们如何得知 state 更新是否在过渡状态呢?从而做出相应的响应样式。
useTransition Hook 返回一个数组,数组第一个值 isPending 标志可以告诉我们 state 更新是否在过渡状态