Skip to content

类型检查

为什么使用 TypeScript?

JavaScript 是动态类型语言,变量类型在运行时才确定,拼写错误、参数类型不对等问题只能在运行时发现。TypeScript 在编译时就能捕获这些错误,在 IDE 中即时提示,大幅减少 Bug 数量。

Nuxt 的类型系统如何工作

理解 Nuxt 的类型系统,能帮你快速定位"为什么类型不对"的问题:

text
你的源码                    Nuxt 自动生成的类型
──────                     ─────────────────
app/components/           → .nuxt/components.d.ts    (组件类型)
app/composables/          → .nuxt/imports.d.ts       (自动导入类型)
app/pages/                → .nuxt/routes.d.ts         (路由类型)
server/api/               → .nuxt/types/nitro.d.ts    (API 类型)
shared/types/             → 两端直接可用
nuxt.config.ts            → .nuxt/types/config.d.ts   (配置类型)

                        ↓ IDE 读取这些 .d.ts 文件 ↓

                    提供自动补全、类型检查、错误提示

核心机制

nuxt prepare 扫描你的项目结构和代码,生成 .nuxt/ 目录下的类型声明。IDE 和 nuxt typecheck 依赖这些声明工作。如果类型不正确,99% 的情况是这些声明过期了。

类型生成的触发时机

操作是否自动生成类型需要手动操作?
nuxt dev✅ 启动时自动生成不需要
npm installpostinstall 钩子自动执行不需要
新建组件/composable✅ HMR 自动更新不需要(但 IDE 可能需要重启 TS 服务)
Git 切换分支❌ 不会自动重新生成需要运行 nuxt prepare
CI 环境❌ 需要手动执行需要运行 nuxt prepare
类型突然丢失❌ 可能是缓存问题运行 nuxt cleanupnuxt prepare

nuxt typecheck

运行 TypeScript 类型检查:

bash
npx nuxt typecheck

TIP

这不会运行你的应用 只检查类型。适合在 CI/CD 中使用,确保代码没有类型错误

nuxt prepare 的区别

命令作用何时用
nuxt prepare生成类型声明文件IDE 类型丢失时、CI 环境中
nuxt typecheck运行类型检查验证代码类型正确性、CI 中

两者关系

nuxt typecheck 内部会先调用 nuxt prepare 确保类型是最新的,然后运行 vue-tsc 检查类型。

package.json 脚本

json
{
  "scripts": {
    "typecheck": "nuxt typecheck"
  }
}

CI/CD 集成

GitHub Actions

yaml
# .github/workflows/ci.yml
name: CI

on: [push, pull_request]

jobs:
  typecheck:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: 20
          cache: 'npm'
      - run: npm ci
      - name: Type Check
        run: npm run typecheck

为什么要在 CI 中加类型检查?

  • 开发时 IDE 可能忽略某些错误(如 any 类型绕过检查)
  • 团队成员可能关闭了严格模式
  • 合并代码前确保类型安全,防止运行时错误

本地 Git Hook

使用 husky + lint-staged 在提交前自动检查:

bash
npm install -D husky lint-staged
npx husky init
json
// package.json
{
  "lint-staged": {
    "*.{ts,vue}": ["nuxt typecheck --no-emit"]
  }
}

INFO

nuxt typecheck 在大型项目中可能较慢(30秒+) 不适合每次提交都运行。建议只在 CI 中运行,或用 tsc --noEmit 只检查修改的文件

严格模式

ts
export default defineNuxtConfig({
  typescript: {
    strict: true,
  },
})

strict: true 具体开启了什么?

检查项说明常见错误示例
noImplicitAny不允许隐式 anyfunction fn(x) {} → x 隐式为 any
strictNullChecksnull/undefined 不能赋值给其他类型const name: string = null → 报错
strictFunctionTypes函数参数双向协变变为逆变函数参数类型不安全时报错
strictBindCallApplybind/call/apply 严格类型fn.call(null, 'wrong') → 报错
strictPropertyInitialization类属性必须初始化class { name: string } → 报错
noImplicitThis不允许隐式 this普通函数中 this 无类型 → 报错
alwaysStrict使用严格模式解析

渐进式采用建议

如果项目已有大量代码,不要一步开启严格模式。先修复 noImplicitAny,再开启其他选项,逐步迁移。

构建时类型检查

ts
export default defineNuxtConfig({
  typescript: {
    typeCheck: true,  // 构建时检查类型,类型错误会导致构建失败
  },
})

INFO

不建议在开发时开启 类型检查会增加构建时间,影响开发体验。推荐在 CI 中使用 npx nuxt typecheck

IDE 支持

VS Code

安装以下扩展:

Vue - Official 2.0+ 已内置 TypeScript 支持

不再需要手动禁用内置 TS 扩展(即下方的 Take Over Mode 已不需要)。

Take Over Mode(旧版 VS Code 才需要)

INFO

️ 如果你使用 Vue - Official 扩展 v2.0+ 不需要以下操作。此步骤仅适用于旧版 Volar 扩展

在 VS Code 设置中禁用内置 TypeScript 扩展:

  1. Ctrl+Shift+PExtensions: Show Built-in Extensions
  2. 找到 TypeScript and JavaScript Language Features
  3. 点击禁用(仅工作区)

常见类型问题与解决方案

1. 自动导入导致的类型丢失

最常见的问题:修改了组合式函数但类型没有更新,IDE 报错或没有自动补全。

原因.nuxt/imports.d.ts 中的类型声明过期了。

bash
# 重新生成类型声明
npx nuxt prepare

什么时候需要手动运行?

场景自动更新?需要手动操作
nuxt dev 运行中修改文件✅ 自动不需要
新建组件/composable✅ 自动(但 IDE 可能延迟)可能需要"重启 TS 服务"
Git 切换分支后需要运行 nuxt prepare
CI 环境中需要运行 nuxt prepare

IDE 快速修复

如果 nuxt prepare 后类型仍然不对,在 VS Code 中按 Ctrl+Shift+PTypeScript: Restart TS Server,强制 IDE 重新加载类型。

2. useFetch 类型推断

ts
// 自动推断(基于 API 路由的返回类型)
const { data } = await useFetch('/api/users')

// 手动指定类型(API 还没写时,或自动推断不正确时)
const { data } = await useFetch<User[]>('/api/users')

INFO

data 可能为 nulluseFetch 初始加载时 datanull,即使指定了泛型类型。在模板中使用前需要做空值检查:<div v-if="data">{{ data.name }}</div>

自动推断什么时候不准?

  • API 路由使用了动态返回类型(如 defineEventHandler(() => result as any)
  • API 路由返回了 any 类型的数据
  • API 路由还没写(文件不存在)
  • 使用了外部 API(非 server/api/ 下的)

3. defineProps 类型

ts
// 推荐:泛型语法(类型安全、IDE 支持好)
defineProps<{
  title: string
  count?: number
}>()

// 带默认值
withDefaults(defineProps<{
  title: string
  count?: number
}>(), {
  count: 0,
})

为什么推荐泛型语法而非运行时声明?

泛型语法有更好的类型推导(IDE 自动补全 Props)、更简洁的语法、withDefaults 提供默认值。运行时声明(defineProps({ title: String }))类型不够精确,无法表示联合类型和复杂类型。

4. 第三方库缺少类型

ts
// 方案 1:安装社区维护的类型包
npm install -D @types/lodash

// 方案 2:自己声明类型
// app/types/shims.d.ts
declare module 'some-untyped-lib' {
  export function doSomething(input: string): number
}

检查顺序

先查 @types/xxx 是否存在(DefinitelyTyped),没有再用 declare moduledeclare module 中的类型需要自己维护,不如社区包准确。

5. 全局类型扩展

为插件 provide 的方法和 runtimeConfig 添加类型:

ts
// app/types/index.d.ts

// 扩展 NuxtApp(插件 provide 的方法)
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 的文件是脚本,其中的 declare 会影响全局。加上 export {} 让文件成为模块,declare module 才能正确扩展指定模块而不是污染全局。

6. 复杂类型场景

泛型组件

vue
<!-- 泛型组件 -->
<script setup lang="ts" generic="T extends { id: number }">
defineProps<{
  items: T[]
  selected?: T
}>()
</script>

generic 属性

Vue 3.3+ 支持在 <script setup> 中声明泛型参数,让你的组件能处理不同类型的数据。

模板 Ref 类型

vue
<script setup lang="ts">
const inputRef = ref<HTMLInputElement | null>(null)

onMounted(() => {
  inputRef.value?.focus()  // ✅ 有类型提示
})
</script>

<template>
  <input ref="inputRef" />
</template>

动态组件类型

vue
<script setup lang="ts">
import type { Component } from 'vue'

// resolveComponent 返回 Component | string,需要断言
const MyComponent = resolveComponent('MyComponent') as Component
</script>

自定义类型声明文件的位置

text
app/
├── types/
│   ├── index.d.ts      # 全局类型扩展(NuxtApp、RuntimeConfig)
│   ├── shims.d.ts      # 第三方库类型补丁
│   └── env.d.ts        # 环境变量类型
shared/
└── types/
    ├── user.ts         # 用户类型(前后端共享)
    └── api.ts          # API 响应类型(前后端共享)

TIP

.d.ts 文件放在 app/types/ 下会被自动识别 如果放在其他位置,确保 tsconfig.jsoninclude 包含该路径。文件末尾需要 export {} 让 TypeScript 将其识别为模块

常见问题

问题原因解决方案
自动导入没有类型提示未执行 nuxt prepare运行 npx nuxt prepare
useFetch 返回 nulldata 初始值为 null使用 v-if="data"data?.name 安全访问
第三方库报类型错误库没有类型声明安装 @types/xxx 或用 declare module 声明
defineProps 类型不生效使用了运行时声明语法改用泛型语法 defineProps<{}>()
修改类型文件后 IDE 不更新IDE 缓存了旧类型重启 TS 服务:Ctrl+Shift+PTypeScript: Restart TS Server
.d.ts 文件不被识别文件位置不对或未导出确保在 tsconfig.jsoninclude 范围内,且末尾有 export {}
Git 切换分支后类型错误.nuxt/ 缓存过期运行 nuxt cleanup && nuxt prepare

知识脉络

text
调试 → 你在这里:类型检查

         ├─→ 相关:TypeScript 支持(03-核心概念/05-TypeScript支持)

         └─→ 相关:shared/types/(02-目录结构/03-shared目录)

基于 Nuxt 4 官方文档整理编写