渲染分页数据是一种非常常见的 UI 模式,在 TanStack Query 中,通过在查询键中包含页面信息,它“就是这样工作”的
const result = useQuery({
queryKey: ['projects', page],
queryFn: fetchProjects,
})
const result = useQuery({
queryKey: ['projects', page],
queryFn: fetchProjects,
})
但是,如果你运行这个简单的例子,你可能会注意到一些奇怪的事情
UI 在 success(成功)和 pending(等待)状态之间跳跃,因为每一页都被视为一个全新的查询。
这种体验不是最佳的,而且不幸的是,当今许多工具都坚持以这种方式工作。但 TanStack Query 不是!正如你可能猜到的,TanStack Query 带有一个名为 placeholderData 的出色功能,它允许我们解决这个问题。
考虑以下示例,我们希望理想情况下为一个查询递增 pageIndex(或游标)。如果我们使用 useQuery,**它仍然可以正常工作**,但 UI 会在 success(成功)和 pending(等待)状态之间跳跃,因为对于每一页或游标都会创建和销毁不同的查询。通过将 placeholderData 设置为 (previousData) => previousData 或从 TanStack Query 导出的 keepPreviousData 函数,我们可以获得一些新的东西。
<script setup lang="ts">
import { ref, Ref } from 'vue'
import { useQuery, keepPreviousData } from '@tanstack/vue-query'
const fetcher = (page: Ref<number>) =>
fetch(
`https://jsonplaceholder.typicode.com/posts?_page=${page.value}&_limit=10`,
).then((response) => response.json())
const page = ref(1)
const { isPending, isError, data, error, isFetching, isPlaceholderData } =
useQuery({
queryKey: ['projects', page],
queryFn: () => fetcher(page),
placeholderData: keepPreviousData,
})
const prevPage = () => {
page.value = Math.max(page.value - 1, 1)
}
const nextPage = () => {
if (!isPlaceholderData.value) {
page.value = page.value + 1
}
}
</script>
<template>
<h1>Posts</h1>
<p>Current Page: {{ page }} | Previous data: {{ isPlaceholderData }}</p>
<button @click="prevPage">Prev Page</button>
<button @click="nextPage">Next Page</button>
<div v-if="isPending">Loading...</div>
<div v-else-if="isError">An error has occurred: {{ error }}</div>
<div v-else-if="data">
<ul>
<li v-for="item in data" :key="item.id">
{{ item.title }}
</li>
</ul>
</div>
</template>
<script setup lang="ts">
import { ref, Ref } from 'vue'
import { useQuery, keepPreviousData } from '@tanstack/vue-query'
const fetcher = (page: Ref<number>) =>
fetch(
`https://jsonplaceholder.typicode.com/posts?_page=${page.value}&_limit=10`,
).then((response) => response.json())
const page = ref(1)
const { isPending, isError, data, error, isFetching, isPlaceholderData } =
useQuery({
queryKey: ['projects', page],
queryFn: () => fetcher(page),
placeholderData: keepPreviousData,
})
const prevPage = () => {
page.value = Math.max(page.value - 1, 1)
}
const nextPage = () => {
if (!isPlaceholderData.value) {
page.value = page.value + 1
}
}
</script>
<template>
<h1>Posts</h1>
<p>Current Page: {{ page }} | Previous data: {{ isPlaceholderData }}</p>
<button @click="prevPage">Prev Page</button>
<button @click="nextPage">Next Page</button>
<div v-if="isPending">Loading...</div>
<div v-else-if="isError">An error has occurred: {{ error }}</div>
<div v-else-if="data">
<ul>
<li v-for="item in data" :key="item.id">
{{ item.title }}
</li>
</ul>
</div>
</template>
虽然不那么常见,但 placeholderData 选项与 useInfiniteQuery hook 配合使用也同样完美,因此你可以无缝地允许用户在 infinite query 键随时间变化时继续查看缓存数据。