Vue Query 支持在服务器上预取多个查询,然后将这些查询<强调>水合强调>到 queryClient。这意味着服务器可以预渲染在页面加载时立即可用的标记,并且一旦 JS 可用,Vue Query 就可以使用库的全部功能升级或<强调>水合强调>这些查询。这包括在客户端重新获取这些查询,如果它们自服务器渲染以来已变得陈旧。
首先在你的 plugins 目录中创建一个 vue-query.ts 文件,内容如下
import type {
DehydratedState,
VueQueryPluginOptions,
} from '@tanstack/vue-query'
import {
VueQueryPlugin,
QueryClient,
hydrate,
dehydrate,
} from '@tanstack/vue-query'
// Nuxt 3 app aliases
import { defineNuxtPlugin, useState } from '#imports'
export default defineNuxtPlugin((nuxt) => {
const vueQueryState = useState<DehydratedState | null>('vue-query')
// Modify your Vue Query global settings here
const queryClient = new QueryClient({
defaultOptions: { queries: { staleTime: 5000 } },
})
const options: VueQueryPluginOptions = { queryClient }
nuxt.vueApp.use(VueQueryPlugin, options)
if (import.meta.server) {
nuxt.hooks.hook('app:rendered', () => {
vueQueryState.value = dehydrate(queryClient)
})
}
if (import.meta.client) {
hydrate(queryClient, vueQueryState.value)
}
})
import type {
DehydratedState,
VueQueryPluginOptions,
} from '@tanstack/vue-query'
import {
VueQueryPlugin,
QueryClient,
hydrate,
dehydrate,
} from '@tanstack/vue-query'
// Nuxt 3 app aliases
import { defineNuxtPlugin, useState } from '#imports'
export default defineNuxtPlugin((nuxt) => {
const vueQueryState = useState<DehydratedState | null>('vue-query')
// Modify your Vue Query global settings here
const queryClient = new QueryClient({
defaultOptions: { queries: { staleTime: 5000 } },
})
const options: VueQueryPluginOptions = { queryClient }
nuxt.vueApp.use(VueQueryPlugin, options)
if (import.meta.server) {
nuxt.hooks.hook('app:rendered', () => {
vueQueryState.value = dehydrate(queryClient)
})
}
if (import.meta.client) {
hydrate(queryClient, vueQueryState.value)
}
})
现在你可以在你的页面中使用 onServerPrefetch 预取一些数据了。
export default defineComponent({
setup() {
const { data, suspense } = useQuery({
queryKey: ['test'],
queryFn: fetcher,
})
onServerPrefetch(async () => {
await suspense()
})
return { data }
},
})
export default defineComponent({
setup() {
const { data, suspense } = useQuery({
queryKey: ['test'],
queryFn: fetcher,
})
onServerPrefetch(async () => {
await suspense()
})
return { data }
},
})
首先在你的 plugins 目录中创建一个 vue-query.js 文件,内容如下
import Vue from 'vue'
import { VueQueryPlugin, QueryClient, hydrate } from '@tanstack/vue-query'
export default (context) => {
// Modify your Vue Query global settings here
const queryClient = new QueryClient({
defaultOptions: { queries: { staleTime: 5000 } },
})
if (process.server) {
context.ssrContext.VueQuery = queryClient
}
if (process.client) {
Vue.use(VueQueryPlugin, { queryClient })
if (context.nuxtState && context.nuxtState.vueQueryState) {
hydrate(queryClient, context.nuxtState.vueQueryState)
}
}
}
import Vue from 'vue'
import { VueQueryPlugin, QueryClient, hydrate } from '@tanstack/vue-query'
export default (context) => {
// Modify your Vue Query global settings here
const queryClient = new QueryClient({
defaultOptions: { queries: { staleTime: 5000 } },
})
if (process.server) {
context.ssrContext.VueQuery = queryClient
}
if (process.client) {
Vue.use(VueQueryPlugin, { queryClient })
if (context.nuxtState && context.nuxtState.vueQueryState) {
hydrate(queryClient, context.nuxtState.vueQueryState)
}
}
}
将此插件添加到你的 nuxt.config.js 中
module.exports = {
...
plugins: ['~/plugins/vue-query.js'],
}
module.exports = {
...
plugins: ['~/plugins/vue-query.js'],
}
现在你可以在你的页面中使用 onServerPrefetch 预取一些数据了。
// pages/todos.vue
<template>
<div>
<button @click="refetch">Refetch</button>
<p>{{ data }}</p>
</div>
</template>
<script lang="ts">
import {
defineComponent,
onServerPrefetch,
useContext,
} from '@nuxtjs/composition-api'
import { useQuery, useQueryClient, dehydrate } from '@tanstack/vue-query'
export default defineComponent({
setup() {
// Get QueryClient either from SSR context, or Vue context
const { ssrContext } = useContext()
// Make sure to provide `queryClient` as a second parameter to `useQuery` calls
const queryClient =
(ssrContext != null && ssrContext.VueQuery) || useQueryClient()
// This will be prefetched and sent from the server
const { data, refetch, suspense } = useQuery(
{
queryKey: ['todos'],
queryFn: getTodos,
},
queryClient,
)
// This won't be prefetched, it will start fetching on client side
const { data2 } = useQuery(
{
queryKey: 'todos2',
queryFn: getTodos,
},
queryClient,
)
onServerPrefetch(async () => {
await suspense()
ssrContext.nuxt.vueQueryState = dehydrate(queryClient)
})
return {
refetch,
data,
}
},
})
</script>
// pages/todos.vue
<template>
<div>
<button @click="refetch">Refetch</button>
<p>{{ data }}</p>
</div>
</template>
<script lang="ts">
import {
defineComponent,
onServerPrefetch,
useContext,
} from '@nuxtjs/composition-api'
import { useQuery, useQueryClient, dehydrate } from '@tanstack/vue-query'
export default defineComponent({
setup() {
// Get QueryClient either from SSR context, or Vue context
const { ssrContext } = useContext()
// Make sure to provide `queryClient` as a second parameter to `useQuery` calls
const queryClient =
(ssrContext != null && ssrContext.VueQuery) || useQueryClient()
// This will be prefetched and sent from the server
const { data, refetch, suspense } = useQuery(
{
queryKey: ['todos'],
queryFn: getTodos,
},
queryClient,
)
// This won't be prefetched, it will start fetching on client side
const { data2 } = useQuery(
{
queryKey: 'todos2',
queryFn: getTodos,
},
queryClient,
)
onServerPrefetch(async () => {
await suspense()
ssrContext.nuxt.vueQueryState = dehydrate(queryClient)
})
return {
refetch,
data,
}
},
})
</script>
如演示所示,预取一些查询并让其他查询在 queryClient 上获取是可以的。这意味着你可以通过为特定查询添加或删除 prefetchQuery 或 suspense 来控制服务器渲染哪些内容。
将 VueQuery 客户端状态与 vite-ssr 同步,以便在 DOM 中序列化它
// main.js (entry point)
import App from './App.vue'
import viteSSR from 'vite-ssr/vue'
import {
QueryClient,
VueQueryPlugin,
hydrate,
dehydrate,
} from '@tanstack/vue-query'
export default viteSSR(App, { routes: [] }, ({ app, initialState }) => {
// -- This is Vite SSR main hook, which is called once per request
// Create a fresh VueQuery client
const queryClient = new QueryClient()
// Sync initialState with the client state
if (import.meta.env.SSR) {
// Indicate how to access and serialize VueQuery state during SSR
initialState.vueQueryState = { toJSON: () => dehydrate(queryClient) }
} else {
// Reuse the existing state in the browser
hydrate(queryClient, initialState.vueQueryState)
}
// Mount and provide the client to the app components
app.use(VueQueryPlugin, { queryClient })
})
// main.js (entry point)
import App from './App.vue'
import viteSSR from 'vite-ssr/vue'
import {
QueryClient,
VueQueryPlugin,
hydrate,
dehydrate,
} from '@tanstack/vue-query'
export default viteSSR(App, { routes: [] }, ({ app, initialState }) => {
// -- This is Vite SSR main hook, which is called once per request
// Create a fresh VueQuery client
const queryClient = new QueryClient()
// Sync initialState with the client state
if (import.meta.env.SSR) {
// Indicate how to access and serialize VueQuery state during SSR
initialState.vueQueryState = { toJSON: () => dehydrate(queryClient) }
} else {
// Reuse the existing state in the browser
hydrate(queryClient, initialState.vueQueryState)
}
// Mount and provide the client to the app components
app.use(VueQueryPlugin, { queryClient })
})
然后,使用 Vue 的 onServerPrefetch 从任何组件调用 VueQuery
<!-- MyComponent.vue -->
<template>
<div>
<button @click="refetch">Refetch</button>
<p>{{ data }}</p>
</div>
</template>
<script setup>
import { useQuery } from '@tanstack/vue-query'
import { onServerPrefetch } from 'vue'
// This will be prefetched and sent from the server
const { refetch, data, suspense } = useQuery({
queryKey: ['todos'],
queryFn: getTodos,
})
onServerPrefetch(suspense)
</script>
<!-- MyComponent.vue -->
<template>
<div>
<button @click="refetch">Refetch</button>
<p>{{ data }}</p>
</div>
</template>
<script setup>
import { useQuery } from '@tanstack/vue-query'
import { onServerPrefetch } from 'vue'
// This will be prefetched and sent from the server
const { refetch, data, suspense } = useQuery({
queryKey: ['todos'],
queryFn: getTodos,
})
onServerPrefetch(suspense)
</script>
任何有错误的查询都会自动从水合中排除。这意味着默认行为是假装这些查询从未在服务器上加载过,通常会显示加载状态,并在 queryClient 上重试查询。无论错误如何,都会发生这种情况。
有时这种行为是不希望的,也许你想要在某些错误或查询时渲染一个带有正确状态代码的错误页面。在这些情况下,使用 fetchQuery 并捕获任何错误以手动处理这些错误。
查询是否被认为是陈旧的取决于它的 dataUpdatedAt 时间。这里需要注意的是,服务器需要有正确的时间才能正常工作,但使用的是 UTC 时间,因此时区不会影响这一点。
由于 staleTime 默认为 0,默认情况下,查询将在页面加载时在后台重新获取。你可能想要使用更高的 staleTime 来避免这种双重获取,特别是如果你不缓存你的标记。
当在 CDN 中缓存标记时,这种重新获取陈旧查询的方式非常匹配!你可以将页面本身的缓存时间设置得相当高,以避免在服务器上重新渲染页面,但将查询的 staleTime 配置得更低,以确保在用户访问页面后立即在后台重新获取数据。也许你想将页面缓存一周,但如果数据超过一天,则在页面加载时自动重新获取数据?
如果你为每个请求都创建 QueryClient,Vue Query 会为这个客户端创建隔离的缓存,该缓存会在内存中保留 gcTime 周期。
在服务器上,gcTime 默认为 Infinity,这会禁用手动垃圾回收,并在请求完成后自动清除内存。如果你显式设置了非 Infinity 的 gcTime,那么你将负责提前清除缓存。
为了在不再需要缓存后清除缓存并降低内存消耗,你可以在请求被处理且水合状态已发送到客户端后添加对 queryClient.clear() 的调用。
或者,你可以设置一个更小的 gcTime。