Vue Query 支持在服务器上预取多个查询,然后将这些查询“脱水”(dehydrate)到 queryClient。这意味着服务器可以预渲染在页面加载时即可使用的标记,并且一旦 JavaScript 可用,Vue Query 就可以用该库的全部功能来“水合”(hydrate)这些查询。这包括在客户端重新获取自服务器渲染以来已变陈旧的查询。
首先,在您的 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。