Доки по разработке
This project is maintained by teniryte
Эта глава отвечает на главный production-вопрос: как убрать client-side waterfall и отдать пользователю готовые данные как можно раньше.
prefetchQueryawait queryClient.prefetchQuery(articleOptions(slug))
prefetchQuery:
Если данные уже свежие, вызов фактически ничего не делает.
ensureQueryDataconst article = await queryClient.ensureQueryData(articleOptions(slug))
Это один из самых полезных API:
function ArticleLink({ slug, children }: { slug: string; children: React.ReactNode }) {
const queryClient = useQueryClient()
const prefetch = () => {
void queryClient.prefetchQuery(articleOptions(slug))
}
return (
<Link href={`/articles/${slug}`} onPointerEnter={prefetch} onFocus={prefetch}>
{children}
</Link>
)
}
Это хороший паттерн для вероятных переходов.
usePrefetchQueryАктуальный API для prefetch во время render перед Suspense boundary:
import { usePrefetchQuery } from '@tanstack/react-query'
function ArticleRoute({ slug }: { slug: string }) {
usePrefetchQuery(articleOptions(slug))
return (
<Suspense fallback={<ArticleSkeleton />}>
<ArticleScreen slug={slug} />
</Suspense>
)
}
Особенности:
dehydrate и HydrationBoundaryСовременный SSR-паттерн:
import {
dehydrate,
HydrationBoundary,
QueryClient,
} from '@tanstack/react-query'
export default async function Page({ params }: { params: { slug: string } }) {
const queryClient = new QueryClient()
await queryClient.prefetchQuery(articleOptions(params.slug))
return (
<HydrationBoundary state={dehydrate(queryClient)}>
<ArticleClient slug={params.slug} />
</HydrationBoundary>
)
}
На клиенте:
'use client'
function ArticleClient({ slug }: { slug: string }) {
const query = useQuery(articleOptions(slug))
return <ArticleView article={query.data} />
}
HydrationBoundary является современным рекомендуемым способом гидратации в React-ветке документации.
initialDataconst postQuery = useQuery({
...postOptions(postId),
initialData: () =>
queryClient
.getQueryData<PostPreview[]>(['posts', 'list'])
?.find((post) => post.id === postId),
initialDataUpdatedAt: () =>
queryClient.getQueryState(['posts', 'list'])?.dataUpdatedAt,
})
Хороший сценарий:
placeholderDataДля переходов между ключами, страницами и фильтрами:
useQuery({
...postsOptions(filters),
placeholderData: (previousData) => previousData,
})
Разница с initialData:
placeholderData не пишет в кешinitialData пишет в кешЧерез dehydrate можно фильтровать:
const dehydratedState = dehydrate(queryClient, {
shouldDehydrateQuery: (query) =>
query.state.status === 'success' &&
query.queryKey[0] !== 'admin-debug',
})
Не стоит сериализовать:
await Promise.all([
queryClient.ensureQueryData(profileOptions(userId)),
queryClient.ensureQueryData(recommendationsOptions(userId)),
queryClient.ensureQueryData(notificationsOptions(userId)),
])
Это стандартный способ убрать waterfall между секциями.
await queryClient.prefetchInfiniteQuery({
queryKey: ['feed'],
initialPageParam: null,
queryFn: ({ pageParam }) => fetchFeed(pageParam),
getNextPageParam: (lastPage) => lastPage.nextCursor,
})
Важно:
initialPageParam должен совпадать на сервере и клиентеgetNextPageParam тоже должен совпадатьstaleTime.