TypeScript 支持
Nuxt 提供零配置的 TypeScript 支持,无需学习 TypeScript 就能享受类型安全。
为什么 Nuxt 对 TypeScript 支持这么好?
| 不用 TypeScript | 用 TypeScript |
|---|---|
useFetch 返回值类型是 any,不知道有哪些字段 | useFetch 返回值有精确类型,IDE 自动补全 |
| 调用 API 时传错了参数类型不会报错 | 传错了类型立即红线提示 |
| 重构代码时怕改漏 | 改了类型定义,所有引用处都报错 |
| 看别人代码不知道函数参数是什么 | 鼠标悬停就能看到类型 |
初学者建议
如果你还不熟悉 TypeScript,可以先用"宽松模式"——在 .vue 文件中用 <script setup lang="ts">,但不写类型注解。TypeScript 会自动推断大部分类型。等你熟悉后再逐步添加类型。
零配置 TypeScript
Nuxt 项目开箱即用 TypeScript:
- 自动生成
tsconfig.json - 自动生成类型声明
.vue文件支持<script setup lang="ts">- 无需手动配置
vue-tsc
// nuxt.config.ts — 只需要这一行
export default defineNuxtConfig({
compatibilityDate: '2025-07-15',
})运行 nuxt prepare 自动生成 .nuxt/tsconfig.json:
// tsconfig.json(你只需要写这一行)
{
"extends": "./.nuxt/tsconfig.json"
}你不需要了解 tsconfig.json 的细节
Nuxt 自动管理它。你只需要知道:类型提示不正常时,运行 nuxt prepare。
类型自动生成
Nuxt 自动为以下内容生成类型:
1. API 路由类型
// server/api/users/index.get.ts
export default defineEventHandler(() => {
return [{ id: 1, name: 'Alice' }, { id: 2, name: 'Bob' }]
})在客户端使用时自动推断响应类型:
<script setup lang="ts">
// data 类型自动推断为 { id: number; name: string }[]
// 不需要手动指定泛型!
const { data } = await useFetch('/api/users')
</script>这是怎么做到的?
Nuxt 扫描 server/api/ 下的文件,分析返回值类型,自动生成类型声明。当你在客户端使用 useFetch('/api/users') 时,TypeScript 知道返回的数据结构。
INFO
️ 前提:你的 API 路由必须有明确的返回值 不能是 any。如果返回类型无法推断(如调用了外部 API),你需要手动指定泛型
2. 组件 Props 类型
<!-- app/components/UserCard.vue -->
<script setup lang="ts">
defineProps<{
name: string
age: number
email?: string // 可选属性
}>()
</script>defineProps<{...}>() 是 Vue 3 的类型式 Props 声明
比对象式声明更简洁:
// 对象式(不推荐)
defineProps({
name: { type: String, required: true },
age: { type: Number, required: true },
email: { type: String, required: false },
})
// 类型式(推荐)
defineProps<{
name: string
age: number
email?: string
}>()3. 自动导入类型
app/components/、app/composables/、app/utils/ 的类型自动生成在:
.nuxt/components.d.ts.nuxt/imports.d.ts
Nuxt 4 的 TypeScript 改进
Nuxt 4 将 TypeScript 项目分为三个独立上下文:
项目根/
├── app/ → .nuxt/tsconfig.app.json
├── server/ → .nuxt/tsconfig.server.json
├── shared/ → 两边都可以用
└── nuxt.config.ts → .nuxt/tsconfig.nuxt.json好处:
- 更准确的类型:前端代码不会看到服务端类型,反之亦然
- 更少的错误:避免在错误上下文中使用 API
- 更快的 IDE:TypeScript 检查范围更小
实际效果
// 在 app/pages/index.vue 中
readBody() // ❌ TypeScript 报错:这是服务端 API,客户端不可用
useState() // ✅ 正确:客户端可用
// 在 server/api/users.ts 中
useState() // ❌ 可能报错:这是客户端 API,服务端不推荐用
readBody() // ✅ 正确:服务端可用Nuxt 4 之前,这种错误只有在运行时才会发现。现在 TypeScript 在编码阶段就能提示。
严格模式
// nuxt.config.ts
export default defineNuxtConfig({
typescript: {
strict: true,
typeCheck: true, // 构建时进行类型检查
},
})要不要开严格模式?
| 情况 | 建议 |
|---|---|
| 刚开始学 Nuxt | 不开,先把功能做出来 |
| 有 TypeScript 基础 | 开,类型安全更可靠 |
| 团队协作 | 必须开,避免低级错误 |
typeCheck: true 会让构建变慢,因为每次构建都要做类型检查。开发时可以关闭,CI/CD 中开启。
常见严格检查问题
// ❌ 隐式 any——函数参数没有类型
function greet(name) { }
// ✅ 显式类型
function greet(name: string) { }// ❌ 可能为 undefined——data.value 可能为 null
const user = data.value.name
// ✅ 安全访问
const name = data.value?.name ?? 'Unknown'类型声明文件
全局类型扩展
// app/types/index.d.ts
// 扩展 NuxtApp,让插件提供的方法有类型提示
declare module '#app' {
interface NuxtApp {
$myPlugin: { doSomething: () => void }
}
}
// 扩展 runtimeConfig,让配置项有类型提示
declare module 'nuxt/schema' {
interface RuntimeConfig {
apiSecret: string
}
interface PublicRuntimeConfig {
apiBase: string
}
}
export {} // 必须有这个,让文件成为模块为什么需要 export {}?
TypeScript 区分"脚本"和"模块"。没有 export 或 import 的文件是脚本,其中的 declare 会影响全局。加上 export {} 让文件成为模块,declare module 才能正确扩展指定模块而不是污染全局。
为 API 响应定义类型
// shared/types/api.ts
export interface ApiResponse<T> {
code: number
message: string
data: T
}
export interface User {
id: number
name: string
email: string
}
export type UserResponse = ApiResponse<User><script setup lang="ts">
import type { UserResponse } from '~~/shared/types/api'
const { data } = await useFetch<UserResponse>('/api/user/1')
// data.value.data 的类型是 User,有完整的类型提示
</script>最佳实践
在 shared/types/ 中定义所有 API 相关类型。服务端和客户端都能使用,确保类型一致。
类型检查命令
# 运行类型检查
npx nuxt typecheck
# 或添加到 package.json scripts
# "typecheck": "nuxt typecheck"推荐在 CI/CD 中加入类型检查
确保每次提交都不会引入类型错误:
# .github/workflows/ci.yml
- run: npm run typecheck在 Vue 组件中使用 TypeScript
<script setup lang="ts">
// Props 类型
const props = defineProps<{
title: string
count?: number
}>()
// Emits 类型
const emit = defineEmits<{
update: [value: string] // 事件名: [参数类型]
delete: [id: number]
}>()
// Ref 类型
const name = ref<string>('')
const items = ref<string[]>([])
// 计算属性自动推断类型
const fullName = computed(() => `${firstName.value} ${lastName.value}`)
// 异步函数
async function fetchData() {
const data = await $fetch<User>('/api/user/1')
// data 类型为 User
}
</script>defineEmits 的新语法
// 旧的调用签名语法(不推荐)
defineEmits<{
(e: 'update', value: string): void
(e: 'delete', id: number): void
}>()
// 新的元组语法(推荐,更简洁)
defineEmits<{
update: [value: string]
delete: [id: number]
}>()常见问题
类型找不到
# 重新生成类型
npx nuxt prepare什么时候需要运行 nuxt prepare?
- 新增了组件/composable 但 IDE 不识别
- Git 切换分支后
npm install后(通常自动运行)- 类型提示突然消失
第三方模块类型缺失
// env.d.ts
declare module 'some-untyped-lib' {
const lib: any
export default lib
}为什么有些库没有类型?
不是所有 npm 包都提供了 TypeScript 类型声明。对于这些库,你需要手动声明类型,或者用 any 作为临时方案。
严格模式下的常见错误
useRoute().params类型不精确:使用getRouteParams或手动断言ts// ❌ params.id 是 string | string[] const id = route.params.id // ✅ 手动断言 const id = route.params.id as string
2. **`useFetch` 返回类型推断失败**:显式指定泛型
```ts
// ❌ 有时推断不出类型
const { data } = await useFetch('/api/users')
// ✅ 手动指定泛型
const { data } = await useFetch<User[]>('/api/users')- 组件 Props 类型推导失败:使用
defineProps<{...}>()而不是对象语法
.vue 文件中 import 路径报错
确保 tsconfig.json 继承了 .nuxt/tsconfig.json,并且运行过 nuxt prepare。
知识脉络
服务引擎 Nitro → 你在这里:TypeScript 支持
│
├─→ 相关:shared/ 目录(类型定义放哪里)
│
└─→ 相关:测试与调试(19章)