TS 快速入门与 Vue 项目配置

文章目录

  • 前言
  • [一、为什么 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

默认已包含:typescriptvue-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 pathsvite 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 等子项。


八、易混淆点

  1. TS ≠ 运行时校验:接口返回仍需约定或运行时 schema。
  2. paths 与 alias 要对齐:tsconfig 与 vite.config 两处配置。
  3. .vue 需要 env.d.ts:否则 import 报找不到模块。
  4. ref 要泛型ref(null) 推断为 Ref<null>,常写 ref<Type | null>(null)
  5. 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 构建