内置 Composables 全景梳理
Nuxt4 提供了 30+ 个内置 Composables,覆盖应用上下文、配置读取、路由导航、数据获取、错误处理、SSR 感知等方方面面。它们不是简单的工具函数——每一个都深度绑定了 Nuxt 的生命周期和 SSR 机制。本章从设计原理出发,梳理核心 Composables 的内部机制、适用场景和 SSR 陷阱。
1. Composables 的本质:为什么不是普通函数
1.1 Vue 组合式 API 的约束
Vue 的 Composables 必须在 setup() 或 <script setup> 中同步调用,原因是它们依赖 Vue 内部的"当前组件实例"来注册生命周期钩子、注入/提供依赖。如果在异步回调或普通函数中调用,Vue 无法关联到正确的组件实例。
// ✅ 正确:在 setup 顶层调用
const route = useRoute()
const { data } = await useFetch('/api/videos')
// ❌ 错误:在异步回调中首次调用
setTimeout(() => {
const route = useRoute() // 可能找不到组件实例
}, 1000)1.2 Nuxt Composables 的额外约束
Nuxt 的内置 Composables 在 Vue 的基础上多了一层——它们通过 useNuxtApp() 访问 Nuxt 应用实例。这意味着:
- 必须在 Nuxt 上下文中调用:组件 setup、插件、路由中间件、Nuxt 生命周期钩子内
- 不能在纯工具函数中调用(除非该函数也在上述上下文中被调用)
- SSR 与 CSR 行为可能不同:部分 Composables 在服务端返回真实数据,在客户端返回 Payload 中的缓存
1.3 分类全景
| 类别 | Composables | 核心用途 |
|---|---|---|
| 应用上下文 | useNuxtApp, useRuntimeConfig, useAppConfig | 访问全局实例和配置 |
| 路由导航 | useRoute, useRouter, navigateTo, abortNavigation | 路由信息与编程导航 |
| 数据获取 | useFetch, useAsyncData, useLazyFetch, useLazyAsyncData | SSR 安全的数据请求 |
| 状态管理 | useState, useNuxtData, clearNuxtData | 跨组件/SSR 状态共享 |
| SEO | useHead, useSeoMeta, useServerSeoMeta | 元数据管理 |
| SSR 感知 | useRequestHeaders, useRequestEvent, useRequestURL | 读取服务端请求信息 |
| 错误处理 | useError, clearError, showError, createError | 全局错误管理 |
| 生命周期 | callOnce, onNuxtReady, onPrehydrate | Nuxt 特有生命周期 |
| Cookie | useCookie | SSR 安全的 Cookie 读写 |
| 预加载 | preloadComponents, prefetchComponents, preloadRouteComponents | 资源预加载 |
2. useNuxtApp:应用上下文的入口
2.1 设计原理
useNuxtApp() 返回 Nuxt 应用的全局实例,它是所有其他 Composables 的基础——内部几乎都通过它来访问共享状态。这个实例在 SSR 阶段是请求级隔离的(每个请求一个实例),在客户端是全局单例。
const nuxtApp = useNuxtApp()
// 核心属性
nuxtApp.vueApp // Vue 应用实例
nuxtApp.ssrContext // SSR 上下文(仅服务端)
nuxtApp.payload // Payload 数据(SSR → 客户端的数据桥梁)
nuxtApp.isHydrating // 是否正在 Hydration
nuxtApp.runWithContext // 在 Nuxt 上下文中执行异步代码2.2 payload:SSR 到客户端的数据桥梁
nuxtApp.payload 是 Nuxt SSR 机制的核心——服务端渲染时,所有通过 useFetch、useState 等获取的数据都会序列化到 Payload 中,随 HTML 发送给浏览器。客户端 Hydration 时直接读取 Payload,避免重复请求。
理解这一点对调试至关重要:如果 Payload 中的数据和客户端预期不一致,就会出现 Hydration Mismatch。
2.3 runWithContext:异步场景的救星
在 setTimeout、事件回调等异步代码中,Nuxt 上下文会丢失。runWithContext 可以手动恢复:
const nuxtApp = useNuxtApp()
// 在异步回调中安全使用 Composables
setTimeout(() => {
nuxtApp.runWithContext(() => {
const route = useRoute() // ✅ 现在可以正常工作
navigateTo('/dashboard')
})
}, 1000)原理:runWithContext 内部会将当前 Nuxt 实例设置为 Vue 的 asyncLocalStorage(Node.js)或全局变量(浏览器),让后续的 Composable 调用能找到正确的上下文。
3. useRuntimeConfig vs useAppConfig
这两个 Composable 都用于"读取配置",但设计理念截然不同:
| 维度 | useRuntimeConfig | useAppConfig |
|---|---|---|
| 定义位置 | nuxt.config.ts 的 runtimeConfig | app.config.ts |
| 是否可在运行时覆盖 | ✅ 通过环境变量 NUXT_* | ❌ 构建时确定 |
| 是否序列化到 Payload | public 部分序列化 | ✅ 完整序列化 |
| 适合存放 | API Key、数据库 URL 等环境相关配置 | 主题色、站点名称等应用配置 |
| 是否响应式 | ❌ | ✅ reactive 对象 |
| 服务端独有数据 | ✅ 私有 key 仅服务端可见 | ❌ 全部对客户端可见 |
关键区分:如果配置需要按环境变化(开发/测试/生产不同值),用 runtimeConfig;如果是应用级常量且需要响应式,用 appConfig。
// app.config.ts — 响应式,可 HMR 热更新
export default defineAppConfig({
ui: { primaryColor: '#3b82f6', borderRadius: '8px' },
site: { name: 'AI 视频平台', logo: '/logo.svg' },
})
// 组件中读取(响应式,修改会触发重渲染)
const appConfig = useAppConfig()
appConfig.ui.primaryColor // '#3b82f6'4. SSR 感知 Composables
这组 Composables 只在服务端有数据,客户端返回空值。它们的存在意义是让服务端代码能读取 HTTP 请求信息。
4.1 useRequestHeaders
读取客户端请求的 HTTP 头,常见场景是将 Cookie 或 Authorization 传递给后端 API:
// 在 SSR 阶段,useFetch 不会自动携带浏览器 Cookie
// 需要手动转发
const headers = useRequestHeaders(['cookie', 'authorization'])
const { data } = await useFetch('/api/user/profile', { headers })为什么需要手动转发? 浏览器请求页面时会携带 Cookie,但 SSR 阶段 Nuxt 向自己的 API 发起的 useFetch 是服务端内部调用(不经过浏览器),不会自动携带原始请求的 Cookie。这是 SSR 新手最常遇到的"用户未登录"问题。
4.2 useRequestEvent
获取底层 H3 Event 对象,可以做更细粒度的操作:
const event = useRequestEvent()
// 仅在服务端有值
if (event) {
const ip = getRequestIP(event)
const url = getRequestURL(event)
setCookie(event, 'visited', 'true')
}4.3 useRequestURL
获取当前请求的完整 URL,SSR 和 CSR 行为一致:
const url = useRequestURL()
// SSR: 从 H3 Event 解析
// CSR: 从 window.location 解析
console.log(url.hostname) // 'example.com'
console.log(url.pathname) // '/videos/123'这个 Composable 比直接用 window.location 安全——它在 SSR 阶段不会报错。
5. useError 与错误处理体系
5.1 Nuxt 错误分类
Nuxt4 将错误分为两类:
- 致命错误(Fatal):触发
error.vue全屏错误页,通过showError()或createError({ fatal: true })触发 - 非致命错误(Non-fatal):不中断页面渲染,通过
<NuxtErrorBoundary>局部捕获
5.2 useError 读取全局错误
const error = useError()
// error.value 是当前的全局错误(如果有的话)
// 类型:{ statusCode, statusMessage, message, data }5.3 错误处理策略
页面级错误:在页面组件中用 showError 抛出:
// pages/videos/[id].vue
const { data: video } = await useFetch(`/api/videos/${route.params.id}`)
if (!video.value) {
showError({ statusCode: 404, statusMessage: '视频不存在' })
}组件级错误:用 <NuxtErrorBoundary> 局部捕获,不影响整个页面:
<template>
<NuxtErrorBoundary @error="handleError">
<VideoPlayer :src="videoUrl" />
<template #error="{ error, clearError }">
<div class="error-fallback">
<p>播放器加载失败:{{ error.message }}</p>
<button @click="clearError">重试</button>
</div>
</template>
</NuxtErrorBoundary>
</template>6. useCookie:SSR 安全的 Cookie 管理
6.1 设计原理
useCookie 是 Nuxt 最精巧的 Composable 之一——它在 SSR 阶段从请求头解析 Cookie,在客户端从 document.cookie 读取,返回一个响应式 Ref。修改它的值会自动设置 Set-Cookie 响应头(SSR)或更新 document.cookie(CSR)。
const token = useCookie('auth-token', {
maxAge: 60 * 60 * 24 * 7, // 7 天
httpOnly: false, // 客户端需要读取
secure: true,
sameSite: 'strict',
})
// 登录后设置
token.value = 'xxx'
// 退出时清除
token.value = null6.2 SSR 与 Hydration 的协同
服务端设置 Cookie 后,值会通过 Payload 传给客户端。客户端 Hydration 时直接读取 Payload 中的值,而非立即解析 document.cookie。这避免了 Hydration Mismatch。
6.3 编码与序列化
useCookie 默认用 JSON 序列化复杂值。如果存储对象,需要注意大小限制(Cookie 单条上限 4KB)。大数据应存 useState 或 localStorage。
7. callOnce 与 onNuxtReady
7.1 callOnce:只执行一次的逻辑
在 SSR 场景下,setup 代码会在服务端和客户端各执行一次。callOnce 确保逻辑只在首次渲染时执行:
// 只在首次访问时记录
await callOnce('analytics-init', async () => {
await $fetch('/api/analytics/pageview', { method: 'POST' })
})它通过 Payload 标记已执行状态——服务端执行后,客户端检查 Payload 发现已执行过,就会跳过。
7.2 onNuxtReady:Hydration 完成后
在客户端 Hydration 完全完成后执行,适合初始化不影响首屏渲染的逻辑:
onNuxtReady(() => {
// Hydration 完成,可以安全操作 DOM 和启动非关键任务
initAnalytics()
registerServiceWorker()
loadChatWidget()
})8. 数据获取 Composables 速览
useFetch 和 useAsyncData 在第 14 章已经深入讲过,这里补充它们在 Composables 体系中的定位:
| Composable | 底层 | 特点 |
|---|---|---|
useFetch(url) | useAsyncData + $fetch | 最常用,自动推导 key |
useAsyncData(key, fn) | — | 基础版,需要手动指定 key 和数据源 |
useLazyFetch | useFetch({ lazy: true }) | 不阻塞导航,适合非关键数据 |
useLazyAsyncData | useAsyncData({ lazy: true }) | 同上 |
关键机制:这四个 Composable 都会将数据存入 nuxtApp.payload,实现 SSR → CSR 的无缝数据传递。客户端导航时(SPA 模式),它们会直接发起客户端请求。
useNuxtData:读取缓存
useNuxtData(key) 可以读取 useFetch / useAsyncData 缓存的数据,无需重新请求:
// 页面 A 中请求了视频列表
const { data } = await useFetch('/api/videos', { key: 'videos' })
// 页面 B 中直接读取缓存(不发起请求)
const { data: cachedVideos } = useNuxtData('videos')这是跨页面共享数据的轻量方案——不需要 Pinia,只要知道 key 即可。
本章小结
- Composables 本质:依赖 Vue 组件实例和 Nuxt 应用上下文,必须在 setup 同步阶段调用
- useNuxtApp:所有 Composables 的基础,
payload是 SSR 数据桥梁,runWithContext解决异步上下文丢失 - runtimeConfig vs appConfig:前者按环境变化(env 覆盖),后者是应用常量(响应式)
- SSR 感知:
useRequestHeaders转发 Cookie 解决 SSR 认证问题,useRequestEvent访问 H3 底层 - 错误处理:Fatal 错误触发
error.vue,非致命错误用<NuxtErrorBoundary>局部捕获 - useCookie:SSR/CSR 统一的响应式 Cookie,值通过 Payload 同步避免 Hydration Mismatch
- callOnce / onNuxtReady:控制代码在 SSR 生命周期中只执行一次或在 Hydration 后执行