文章目录
- 前言
- [一、为什么 Vue 3 要用 TypeScript](#一、为什么 Vue 3 要用 TypeScript)
-
- [1.1 解决的问题](#1.1 解决的问题)
- [1.2 与 Vue 3 的配合](#1.2 与 Vue 3 的配合)
- [1.3 TS 不会做的事](#1.3 TS 不会做的事)
- 二、基础类型
-
- [2.1 原始类型](#2.1 原始类型)
- [2.2 类型注解 vs 类型推断](#2.2 类型注解 vs 类型推断)
- [2.3 联合类型与可选](#2.3 联合类型与可选)
- [2.4 常用类型别名与接口](#2.4 常用类型别名与接口)
- 三、any、unknown、never
-
- [3.1 any:放弃检查](#3.1 any:放弃检查)
- [3.2 unknown:安全的 any](#3.2 unknown:安全的 any)
- [3.3 never:永不存在的值](#3.3 never:永不存在的值)
- [四、Vue + Vite 项目 TS 配置](#四、Vue + Vite 项目 TS 配置)
-
- [4.1 创建项目](#4.1 创建项目)
- [4.2 tsconfig.json 核心项](#4.2 tsconfig.json 核心项)
- [4.3 路径别名对齐](#4.3 路径别名对齐)
- [4.4 env.d.ts 声明 .vue](#4.4 env.d.ts 声明 .vue)
- [4.5 环境变量类型](#4.5 环境变量类型)
- 五、工具链:谁在做类型检查
-
- [5.1 Volar(Vue - Official)](#5.1 Volar(Vue - Official))
- [5.2 vue-tsc](#5.2 vue-tsc)
- [5.3 开发流程](#5.3 开发流程)
- [六、Vue 组件中的 TS 入门](#六、Vue 组件中的 TS 入门)
-
- [6.1 script setup lang="ts"](#6.1 script setup lang="ts")
- [6.2 简单 props 类型(预告)](#6.2 简单 props 类型(预告))
- 七、面试聚焦
-
- [7.1 any vs unknown vs never](#7.1 any vs unknown vs never)
- [7.2 TS 在 Vue 项目里谁做类型检查?](#7.2 TS 在 Vue 项目里谁做类型检查?)
- [7.3 strict: true 开不开?](#7.3 strict: true 开不开?)
- 八、易混淆点
- 九、思考与练习
- 总结
前言
Vue 3 项目普遍采用 TypeScript,配合 Vite 与 <script setup lang="ts"> 可在开发阶段发现类型错误,提升重构与协作效率。本篇是 TS + Vue 3 系列 的开篇,讲清楚:
- 为什么 Vue 3 项目推荐 TypeScript
- 基础类型与常见写法
- Vue + Vite 项目的 TS 配置
一、为什么 Vue 3 要用 TypeScript
1.1 解决的问题
| 痛点(纯 JS) | TS 的改善 |
|---|---|
| Props 传错字段名,运行时才报错 | 编译期检查 props 类型 |
| 重构改字段名,引用处难找全 | IDE 跳转、批量重命名 |
| API 返回结构靠记忆 | 接口类型约束 data |
| composable 返回值不清晰 | 明确入参/返回类型 |
1.2 与 Vue 3 的配合
- defineProps / defineEmits:可声明类型,模板里自动校验
- ref / reactive :泛型标注
ref<User | null>(null) - Pinia:Store 完整类型推导
- 模板类型检查 :Volar 对
.vue模板中的变量做检查(需开启)
vue
<script setup lang="ts">
const count = ref(0)
// count.value = 'abc' // ❌ TS 报错:不能赋 string
</script>
1.3 TS 不会做的事
- 不会在浏览器里运行:编译/检查阶段使用,打包后仍是 JS
- 不能替代运行时校验:后端数据仍需接口约定或 zod 等校验
- 不是必须:小 demo 可用 JS;中后台、团队协作项目强烈建议 TS
二、基础类型
2.1 原始类型
typescript
let name: string = 'Vue'
let age: number = 3
let active: boolean = true
let nothing: null = null
let notAssigned: undefined = undefined
// 数组
let ids: number[] = [1, 2, 3]
let names: Array<string> = ['a', 'b']
// 元组(固定长度、固定类型)
let tuple: [string, number] = ['id', 1]
// 枚举
enum Status {
Pending = 0,
Success = 1,
Failed = 2
}
2.2 类型注解 vs 类型推断
typescript
// 类型推断:TS 根据初始值自动推断
let count = 0 // number
let title = 'Hello' // string
// 类型注解:显式声明(无初始值、需要拓宽类型时常用)
let userId: number
let list: string[] = []
// Vue 中常见
const msg = ref('hi') // Ref<string>
const user = ref<User | null>(null) // 需注解或泛型参数
原则:能推断就不写;推断不准或需要约束时再写注解。
2.3 联合类型与可选
typescript
// 联合类型:多种类型之一
let id: string | number = 1
id = 'abc'
// 可选属性
interface User {
id: number
name: string
email?: string // 等价于 email: string | undefined
}
// 可选链与 TS
function printEmail(user: User) {
console.log(user.email?.toLowerCase())
}
2.4 常用类型别名与接口
typescript
// interface:描述对象形状,可 extends
interface BaseUser {
id: number
}
interface Admin extends BaseUser {
role: 'admin'
}
// type:联合、交叉、工具类型更灵活
type ID = string | number
type Role = 'admin' | 'user' | 'guest'
// Vue 项目里:实体用 interface,联合/别名用 type 均可
三、any、unknown、never
3.1 any:放弃检查
typescript
let data: any = fetchSomething()
data.foo.bar // 不报错,运行可能崩溃
原则 :业务代码尽量避免 any;迁移老 JS 时可临时使用,逐步替换。
3.2 unknown:安全的 any
typescript
let input: unknown = getInput()
// ❌ 不能直接使用
// input.length
// ✅ 先收窄类型
if (typeof input === 'string') {
console.log(input.length)
}
适合接收不确定来源的数据(接口 JSON、第三方库)。
3.3 never:永不存在的值
typescript
// 抛错函数
function fail(msg: string): never {
throw new Error(msg)
}
// exhaustive check(联合类型穷尽)
type Shape = 'circle' | 'square'
function area(shape: Shape) {
switch (shape) {
case 'circle': return Math.PI
case 'square': return 1
default:
const _exhaustive: never = shape
return _exhaustive
}
}
| 类型 | 含义 | 使用场景 |
|---|---|---|
any |
任意类型,不检查 | 遗留代码、快速原型(慎用) |
unknown |
未知类型,使用前须收窄 | 外部数据、catch 错误 |
never |
不可能有值 | 抛错、穷尽检查 |
四、Vue + Vite 项目 TS 配置
4.1 创建项目
bash
npm create vite@latest my-vue-app -- --template vue-ts
cd my-vue-app
npm install
默认已包含:typescript、vue-tsc、@vitejs/plugin-vue。
4.2 tsconfig.json 核心项
json
{
"compilerOptions": {
"target": "ES2020",
"module": "ESNext",
"moduleResolution": "bundler",
"strict": true,
"jsx": "preserve",
"resolveJsonModule": true,
"isolatedModules": true,
"skipLibCheck": true,
"noEmit": true,
"baseUrl": ".",
"paths": {
"@/*": ["src/*"]
},
"types": ["vite/client"]
},
"include": ["src/**/*.ts", "src/**/*.tsx", "src/**/*.vue"],
"references": [{ "path": "./tsconfig.node.json" }]
}
| 配置项 | 作用 |
|---|---|
strict: true |
开启严格模式(新项目建议开) |
moduleResolution: bundler |
适配 Vite 的 ESM 解析 |
paths |
路径别名,与 vite.config 的 alias 一致 |
noEmit: true |
只做类型检查,不输出 JS(Vite 负责构建) |
skipLibCheck: true |
跳过 node_modules 类型检查,加快速度 |
4.3 路径别名对齐
typescript
// vite.config.ts
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import { fileURLToPath, URL } from 'node:url'
export default defineConfig({
plugins: [vue()],
resolve: {
alias: {
'@': fileURLToPath(new URL('./src', import.meta.url))
}
}
})
tsconfig paths 与 vite alias 必须一致,否则 IDE 能跳转但构建报错,或反之。
4.4 env.d.ts 声明 .vue
typescript
// src/env.d.ts
/// <reference types="vite/client" />
declare module '*.vue' {
import type { DefineComponent } from 'vue'
const component: DefineComponent<object, object, unknown>
export default component
}
让 TS 识别 import App from './App.vue'。
4.5 环境变量类型
typescript
// env.d.ts 扩展
interface ImportMetaEnv {
readonly VITE_API_BASE: string
readonly VITE_APP_TITLE: string
}
interface ImportMeta {
readonly env: ImportMetaEnv
}
typescript
// 使用
const baseURL = import.meta.env.VITE_API_BASE
五、工具链:谁在做类型检查
5.1 Volar(Vue - Official)
- VS Code / Cursor 插件:Vue - Official(原 Volar)
- 提供
.vue文件 TS 支持、模板类型检查、Props 跳转 - Takeover Mode:禁用内置 TS 插件,由 Volar 接管 TS(可选,按官方文档配置)
5.2 vue-tsc
bash
# 仅类型检查,不打包
npm run type-check
# 通常对应 package.json:
# "type-check": "vue-tsc --build --force"
- 构建前 CI 跑
vue-tsc,防止类型错误上线 - 与
vite build配合:build 不检查类型,需单独脚本
5.3 开发流程
编写 .vue / .ts
↓
IDE 实时报错(Volar)
↓
提交前 / CI:vue-tsc
↓
vite build 产出 JS
六、Vue 组件中的 TS 入门
6.1 script setup lang="ts"
vue
<script setup lang="ts">
import { ref, computed } from 'vue'
interface Todo {
id: number
title: string
done: boolean
}
const list = ref<Todo[]>([])
const activeCount = computed(() => list.value.filter(t => !t.done).length)
const add = (title: string) => {
list.value.push({ id: Date.now(), title, done: false })
}
</script>
<template>
<p>未完成:{{ activeCount }}</p>
</template>
6.2 简单 props 类型(预告)
vue
<script setup lang="ts">
defineProps<{
title: string
count?: number
}>()
</script>
Props / Emits 的完整写法在系列第 2 篇展开。
七、面试聚焦
7.1 any vs unknown vs never
- any:不检查,慎用
- unknown:未知,使用前类型收窄
- never:不可能的值,用于抛错与穷尽检查
7.2 TS 在 Vue 项目里谁做类型检查?
Volar(IDE 实时)+ vue-tsc(CI/脚本);运行时没有 TS。
7.3 strict: true 开不开?
新项目建议开 ;迁移老项目可逐步开启 strictNullChecks 等子项。
八、易混淆点
- TS ≠ 运行时校验:接口返回仍需约定或运行时 schema。
- paths 与 alias 要对齐:tsconfig 与 vite.config 两处配置。
- .vue 需要 env.d.ts:否则 import 报找不到模块。
- ref 要泛型 :
ref(null)推断为Ref<null>,常写ref<Type | null>(null)。 - vite build 不跑类型检查 :记得加
type-check脚本。
九、思考与练习
1. 为什么 Vue 3 项目推荐 TypeScript?
解析:Props/API/重构有编译期保障,IDE 体验好,团队协作成本更低。
2. 类型推断和类型注解如何选择?
解析:有初始值且推断正确可不写;无初始值、需要联合类型、泛型参数时用注解。
3. unknown 和 any 的区别?
解析:any 放弃检查;unknown 使用前必须收窄,更安全。
4. vue-tsc 和 vite build 各做什么?
解析:vue-tsc 只做类型检查;vite build 打包产出,默认不包含类型检查。
5. 路径别名 @/ 要配置几处?
解析:vite.config.ts 的 alias + tsconfig.json 的 paths,两处一致。
总结
- TS 价值:编译期发现错误、重构安全、API 与 Props 有类型约束
- 基础类型:string/number/boolean、数组、联合、可选属性
- any / unknown / never:慎用 any,优先 unknown + 收窄
- Vue 配置:strict、paths、env.d.ts、环境变量类型扩展
- 工具链:Volar 开发体验 + vue-tsc CI 检查 + Vite 构建