React Query 的 useQuery 竟也內(nèi)置了分頁(yè)查詢支持!
本次我們將繼續(xù) useQuery() API 的學(xué)習(xí),著重講述 useQuery() 在分頁(yè)上的優(yōu)化能力[3]。
基本分頁(yè)功能實(shí)現(xiàn)
項(xiàng)目中通常會(huì)遇到分頁(yè)查詢的需要,通過(guò)之前的學(xué)習(xí),我們會(huì)寫出這樣的代碼。
function Example() {
const [page, setPage] = useState(1)
const { isLoading, isError, error, data: posts } = useQuery(
['posts', page],
() => axios.get('https://jsonplaceholder.typicode.com/posts', { params: { _page: page, _limit: 6 } })
)
return (
<div>
<h2>Posts(第{page}頁(yè))</h2>
{
isLoading && <p>Loading....</p>
}
{
isError && <p>An error has occurred: {error.message}</p>
}
<ul>
{
posts?.data.map(post => (
<li key={post.id}>{post.title}</li>
))
}
</ul>
<div style={{ display: 'flex', gap: '8px' }}>
<button disabled={isLoading} onClick={() => setPage(prePage => Math.max(0, prePage - 1))}>Previous Page</button>
<button disabled={isLoading} onClick={() => setPage(prePage => prePage + 1)}>Next Page</button>
</div>
</div>
)
}
瀏覽器訪問(wèn):
圖片
點(diǎn)擊“Next Page”查看下一頁(yè)數(shù)據(jù)。
圖片
確實(shí)也實(shí)現(xiàn)了功能,不過(guò)體驗(yàn)上不好的地方在于,每次切換到新一頁(yè)數(shù)據(jù)時(shí),中間會(huì)間隔一個(gè)“Loading...”效果,導(dǎo)致頁(yè)面閃動(dòng)。
為了不然頁(yè)面閃動(dòng),我們可以考慮在加載新頁(yè)面數(shù)據(jù)時(shí),同時(shí)保留舊數(shù)據(jù)的展示不就行了嗎?后面一旦新數(shù)據(jù)到了,直接替換就行。
useQuery() API 可考慮到了這方面的使用體驗(yàn),于是便提供了一個(gè) keepPreviousData 選項(xiàng)。
保留舊數(shù)據(jù)的分頁(yè)功能
我們?cè)谥鞍咐幕A(chǔ)之上,調(diào)用 useQuery() 時(shí),指定 keepPreviousData: true 選項(xiàng)。
const { isLoading, isError, error, data: posts } = useQuery(
['posts', page],
() => axios.get('https://jsonplaceholder.typicode.com/posts', { params: { _page: page, _limit: 6 } }),
{
keepPreviousData: true
}
)
刷新頁(yè)面,再來(lái)看看加載新頁(yè)的效果。
圖片
發(fā)現(xiàn)頁(yè)面不再閃動(dòng)了。
不過(guò),這種狀態(tài)下的中間狀態(tài)我們還要細(xì)致的控制一下,比如請(qǐng)求過(guò)程中禁用按鈕的點(diǎn)擊能力。
- const { isLoading, isError, error, data: posts } = useQuery()
+ const { isLoading, isError, isFetching, error, data: posts } = useQuery()
<div style={{ display: 'flex', gap: '8px' }}>
- <button disabled={isLoading} notallow={() => setPage(prePage => Math.max(0, prePage - 1))}>Previous Page</button>
- <button disabled={isLoading} notallow={() => setPage(prePage => prePage + 1)}>Next Page</button>
+ <button disabled={isLoading || isFetching} notallow={() => setPage(prePage => Math.max(0, prePage - 1))}>Previous Page</button>
+ <button disabled={isLoading || isFetching} notallow={() => setPage(prePage => prePage + 1)}>Next Page</button>
</div>
再次查看效果:
圖片
不過(guò),除了 isFetching,useQuery() 還返回了一個(gè) isPreviousData 狀態(tài),讓你判斷當(dāng)前展示的是不是舊數(shù)據(jù)。
- const { isLoading, isError, isFetching, error, data: posts } = useQuery()
+ const { isLoading, isError, isPreviousData, isFetching, error, data: posts } = useQuery()
- <ul>
+ <ul style={{ opacity: isPreviousData ? 0.5 : 1 }}>
{
posts?.data.map(post => (
<li key={post.id}>{post.title}</li>
))
}
</ul>
再次查看效果:
圖片
這樣舊數(shù)據(jù)在展示期間,加了一點(diǎn)點(diǎn)透明效果。
至此,我們就講完了 useQuery() 的分頁(yè)優(yōu)化能力。
總結(jié)
本文我講解了另一個(gè) useQuery() 能力——keepPreviousData。
在設(shè)置 keepPreviousData: true 后,我們?cè)谶M(jìn)行分頁(yè)查詢時(shí),同時(shí)能保持舊數(shù)據(jù)的展示,避免頁(yè)面閃動(dòng)。
本質(zhì)上,keepPreviousData 選項(xiàng)是 useQuery() 針對(duì)分頁(yè)場(chǎng)景上的一個(gè)優(yōu)化能力。
當(dāng)然,UI 交互中還有一種特殊的分頁(yè)場(chǎng)景,即無(wú)限查詢(Infinite Query)。這在“上滑/上拉查看歷史數(shù)據(jù)”,或者“下滑/下拉查看最新數(shù)據(jù)”被廣泛采用,不過(guò) useQuery 是解決不了的了,這要靠 useInfiniteQuery()。
參考資料
[1]React Query 是做什么的?: https://juejin.cn/post/7378015213348257855
[2]一個(gè)數(shù)據(jù)獲竟然被 React Query 玩出這么多花樣來(lái)!: https://juejin.cn/post/7380342160581918731
[3]useQuery() 在分頁(yè)上的優(yōu)化能力: https://tanstack.com/query/v3/docs/framework/react/guides/paginated-queries