> ## Content Index
> Fetch the complete content index at: https://blog.vercanti.com/llms.txt
> Use this file to discover other available public pages before exploring further.

# TypeScript 完全指南
- URL: https://blog.vercanti.com/typescript-wan-quan-zhi-nan/
- Published: 2026-08-28T14:35:21.000Z
- Updated: 2026-08-28T14:58:40.000Z
- Description: 相关文档：JavaScript入门(/javascript-ru-men/) | Vue3入门(/vue-3-ru-men-zhi-nan/) | JS模块系统(/js-mjs-ts-wen-jian-qu-bie-yu-zui-jia-shi-jian/) 1. 基础类型系统(#%E4%B8%80%E3%80%81%E5%9F%BA%E7%A1%80%E7%B1%BB%E5%9E%8B%E7%B3%BB%E7%BB%9F) 2. 函数类型(#%E4%BA%8C%E3%80%81%E5%87%BD%E6%95%B0%E7%B1%BB%E5%9E%8B)
- Author: yellowdog
- Tags: 前端开发

> 官方文档：<https://www.typescriptlang.org/docs/>  
> TypeScript Playground：<https://www.typescriptlang.org/play> | tsconfig 参考：<https://www.typescriptlang.org/tsconfig>  
> 适用版本：TypeScript 5.x（2026-05-07 核实）

相关文档：[JavaScript入门](https://blog.vercanti.com/javascript-ru-men/) | [Vue3入门](https://blog.vercanti.com/vue-3-ru-men-zhi-nan/) | [JS模块系统](https://blog.vercanti.com/js-mjs-ts-wen-jian-qu-bie-yu-zui-jia-shi-jian/)

---

## 目录

1. [基础类型系统](#%E4%B8%80%E3%80%81%E5%9F%BA%E7%A1%80%E7%B1%BB%E5%9E%8B%E7%B3%BB%E7%BB%9F)
2. [函数类型](#%E4%BA%8C%E3%80%81%E5%87%BD%E6%95%B0%E7%B1%BB%E5%9E%8B)
3. [接口与类型别名](#%E4%B8%89%E3%80%81%E6%8E%A5%E5%8F%A3%E4%B8%8E%E7%B1%BB%E5%9E%8B%E5%88%AB%E5%90%8D)
4. [类](#%E5%9B%9B%E3%80%81%E7%B1%BB)
5. [泛型](#%E4%BA%94%E3%80%81%E6%B3%9B%E5%9E%8B)
6. [高级类型](#%E5%85%AD%E3%80%81%E9%AB%98%E7%BA%A7%E7%B1%BB%E5%9E%8B)
7. [模块与命名空间](#%E4%B8%83%E3%80%81%E6%A8%A1%E5%9D%97%E4%B8%8E%E5%91%BD%E5%90%8D%E7%A9%BA%E9%97%B4)
8. [装饰器](#%E5%85%AB%E3%80%81%E8%A3%85%E9%A5%B0%E5%99%A8)
9. [类型声明文件](#%E4%B9%9D%E3%80%81%E7%B1%BB%E5%9E%8B%E5%A3%B0%E6%98%8E%E6%96%87%E4%BB%B6)
10. [tsconfig.json 完整配置](#%E5%8D%81%E3%80%81tsconfigjson-%E5%AE%8C%E6%95%B4%E9%85%8D%E7%BD%AE)
11. [Vue 3 + TypeScript 最佳实践](#%E5%8D%81%E4%B8%80%E3%80%81vue-3-typescript-%E6%9C%80%E4%BD%B3%E5%AE%9E%E8%B7%B5)
12. [项目文件结构](#%E5%8D%81%E4%BA%8C%E3%80%81%E9%A1%B9%E7%9B%AE%E6%96%87%E4%BB%B6%E7%BB%93%E6%9E%84)
13. [最佳实践](#%E5%8D%81%E4%B8%89%E3%80%81%E6%9C%80%E4%BD%B3%E5%AE%9E%E8%B7%B5)
14. [常见陷阱与注意事项](#%E5%8D%81%E5%9B%9B%E3%80%81%E5%B8%B8%E8%A7%81%E9%99%B7%E9%98%B1%E4%B8%8E%E6%B3%A8%E6%84%8F%E4%BA%8B%E9%A1%B9)

---

## 一、基础类型系统

### 原始类型

```ts
// 基本类型注解
let name: string = 'Alice'
let age: number = 25
let active: boolean = true
let big: bigint = 100n
let sym: symbol = Symbol('key')

// 空值类型
let nothing: null = null
let missing: undefined = undefined
let voidFn: void = undefined   // 通常用于函数无返回值

// 顶层类型
let anything: any = '可以是任何值，跳过类型检查'
let unknown: unknown = getData()  // 比 any 安全，使用前需收窄

// 底层类型
function throwError(msg: string): never {
    throw new Error(msg)          // never：永不返回的函数
}

// object 类型（非原始类型）
let obj: object = { a: 1 }

```

### 数组与元组

```ts
// 数组（两种等价写法）
let nums: number[] = [1, 2, 3]
let strs: Array<string> = ['a', 'b', 'c']

// 只读数组
let frozen: readonly number[] = [1, 2, 3]
let frozen2: ReadonlyArray<number> = [1, 2, 3]
frozen.push(4)   // Error: Property 'push' does not exist on type 'readonly number[]'

// 元组：固定长度和类型的数组
let pair: [string, number] = ['Alice', 25]
let triple: [string, number, boolean] = ['Bob', 30, true]

// 可选元素元组
let optional: [string, number?] = ['Alice']

// 剩余元素元组
let rest: [string, ...number[]] = ['Alice', 1, 2, 3]

// 具名元组（TypeScript 4.0+）
let named: [name: string, age: number] = ['Alice', 25]

```

### 字面量类型

```ts
// 字符串字面量
let direction: 'left' | 'right' | 'up' | 'down' = 'left'

// 数字字面量
let statusCode: 200 | 201 | 400 | 404 | 500 = 200

// 布尔字面量
let alwaysTrue: true = true

// 字面量推断问题
const config = { method: 'GET' }          // 推断为 { method: string }
const config2 = { method: 'GET' } as const // 推断为 { readonly method: "GET" }

// 模板字面量类型（TypeScript 4.1+）
type EventName = `on${Capitalize<string>}`
type CSSUnit = `${number}${'px' | 'rem' | 'em' | '%'}`
type Width = `${number}px`
let w: Width = '100px'

```

### 类型推断

```ts
// TypeScript 会自动推断类型，大多数情况无需显式注解
let x = 42                    // 推断为 number
let y = 'hello'               // 推断为 string
let z = [1, 2, 3]             // 推断为 number[]
let fn = (a: number) => a * 2 // 返回值推断为 number

// 上下文推断
window.addEventListener('click', (e) => {
    // e 被推断为 MouseEvent（TypeScript 知道 click 事件的类型）
    console.log(e.clientX)
})

```

### 类型断言

```ts
// as 语法（推荐）
const input = document.getElementById('username') as HTMLInputElement
const value = input.value

// 尖括号语法（不能在 .tsx 文件中使用）
const input2 = <HTMLInputElement>document.getElementById('username')

// 双重断言（先断言为 unknown，再断言为目标类型）
// 仅在确实无法直接断言时使用
const x = 'hello' as unknown as number   // 不推荐，除非必要

// satisfies 运算符（TypeScript 4.9+）
// 验证类型而不改变推断结果
const palette = {
    red: [255, 0, 0],
    green: '#00ff00',
} satisfies Record<string, string | number[]>

palette.red.map(x => x)       // 正确：red 仍推断为 number[]
palette.green.toUpperCase()   // 正确：green 仍推断为 string

```

---

## 二、函数类型

### 函数声明与注解

```ts
// 参数类型 + 返回值类型
function add(a: number, b: number): number {
    return a + b
}

// 可选参数（必须在必填参数之后）
function greet(name: string, greeting?: string): string {
    return `${greeting ?? 'Hello'}, ${name}!`
}

// 默认参数
function createUser(name: string, role: string = 'user'): object {
    return { name, role }
}

// 剩余参数
function sum(...nums: number[]): number {
    return nums.reduce((acc, n) => acc + n, 0)
}

// 箭头函数
const multiply = (a: number, b: number): number => a * b

// 函数类型表达式
type BinaryOp = (a: number, b: number) => number
const add2: BinaryOp = (a, b) => a + b

```

### 函数重载

```ts
// 重载签名（只有声明，无实现）
function process(input: string): string
function process(input: number): number
function process(input: string[]): string

// 实现签名（处理所有情况，参数/返回类型足够宽泛）
function process(input: string | number | string[]): string | number {
    if (typeof input === 'string') return input.toUpperCase()
    if (typeof input === 'number') return input * 2
    return input.join(', ')
}

// 调用时 TypeScript 匹配重载签名
const a = process('hello')    // 类型为 string
const b = process(42)         // 类型为 number

```

### this 类型

```ts
// 显式声明 this 的类型（TypeScript 的伪参数，不计入实际参数）
function onClick(this: HTMLButtonElement, event: MouseEvent): void {
    console.log(this.textContent)
}

// 对象方法中的 this
interface Counter {
    count: number
    increment(this: Counter): void
}

```

### 函数类型工具

```ts
// Parameters<T>：获取函数参数类型元组
function fetchUser(id: number, token: string): Promise<User> { ... }
type FetchParams = Parameters<typeof fetchUser>   // [id: number, token: string]

// ReturnType<T>：获取函数返回类型
type FetchResult = ReturnType<typeof fetchUser>   // Promise<User>

// ConstructorParameters<T>：获取构造函数参数类型
class User { constructor(name: string, age: number) {} }
type UserArgs = ConstructorParameters<typeof User>   // [name: string, age: number]

```

---

## 三、接口与类型别名

### interface

```ts
// 基础接口
interface User {
    readonly id: number          // 只读
    name: string
    email?: string               // 可选
    createdAt: Date
}

// 方法签名
interface Repository<T> {
    findById(id: number): Promise<T>
    findAll(): Promise<T[]>
    save(entity: T): Promise<T>
    delete(id: number): Promise<void>
}

// 索引签名
interface StringMap {
    [key: string]: string        // 任意字符串键，值为 string
}

interface NumberArray {
    [index: number]: number      // 数字索引，值为 number
}

// 接口继承（支持多继承）
interface Animal {
    name: string
    eat(): void
}

interface Pet {
    owner: string
}

interface Dog extends Animal, Pet {
    breed: string
    bark(): void
}

// 接口合并（同名接口自动合并，重要特性）
interface Window {
    myGlobal: string
}
interface Window {
    anotherGlobal: number
}
// 结果：Window 同时拥有 myGlobal 和 anotherGlobal

```

### type 类型别名

```ts
// 基础别名
type ID = string | number
type Nullable<T> = T | null
type Callback = () => void

// 对象类型
type Point = {
    x: number
    y: number
}

// 交叉类型（合并多个类型）
type AdminUser = User & { role: 'admin'; permissions: string[] }

// 联合类型
type StringOrNumber = string | number
type Status = 'pending' | 'active' | 'inactive'

// 条件类型（见高级类型章节）
type IsString<T> = T extends string ? true : false

// 映射类型（见高级类型章节）
type Optional<T> = { [K in keyof T]?: T[K] }

```

### interface vs type 选择原则

| 场景             | 推荐                  | 原因               |
| -------------- | ------------------- | ---------------- |
| 定义对象/类的结构      | interface           | 可被继承、可合并扩展       |
| 描述函数签名         | type 或 interface 均可 | 风格统一即可           |
| 联合类型、元组类型      | type                | interface 不支持    |
| 交叉类型           | type                | interface 的继承有限制 |
| 需要声明合并（扩展第三方库） | interface           | type 不支持同名合并     |
| 工具类型（映射/条件类型）  | type                | interface 不支持    |

---

## 四、类

### 基本语法

```ts
class Animal {
    // 属性声明（TypeScript 要求先声明）
    name: string
    private age: number
    protected species: string
    readonly id: number
    static count: number = 0

    constructor(name: string, age: number) {
        this.name = name
        this.age = age
        this.species = 'Unknown'
        this.id = Math.random()
        Animal.count++
    }

    // 实例方法
    speak(): string {
        return `${this.name} makes a sound`
    }

    // getter / setter
    get info(): string {
        return `${this.name} (${this.age})`
    }

    set nickname(value: string) {
        this.name = value
    }

    // 静态方法
    static getCount(): number {
        return Animal.count
    }
}

```

### 访问修饰符

| 修饰符                 | 类内部       | 子类   | 类外部  | 说明      |
| ------------------- | --------- | ---- | ---- | ------- |
| public（默认）          | 可访问       | 可访问  | 可访问  | 公开      |
| protected           | 可访问       | 可访问  | 不可访问 | 类及子类内部  |
| private             | 可访问       | 不可访问 | 不可访问 | 仅类内部    |
| #privateField（JS私有） | 可访问       | 不可访问 | 不可访问 | 运行时真正私有 |
| readonly            | 只能在构造函数赋值 | —    | 只读   | 不可修改    |

```ts
class BankAccount {
    readonly id: string
    private balance: number
    #pin: number                          // JS 原生私有字段（运行时私有）

    constructor(initialBalance: number, pin: number) {
        this.id = crypto.randomUUID()
        this.balance = initialBalance
        this.#pin = pin
    }

    withdraw(amount: number, pin: number): boolean {
        if (pin !== this.#pin) return false
        if (amount > this.balance) return false
        this.balance -= amount
        return true
    }
}

```

### 构造函数参数简写

```ts
// 不简写（冗余）
class User {
    public name: string
    private age: number
    constructor(name: string, age: number) {
        this.name = name
        this.age = age
    }
}

// 简写（在参数前加修饰符，自动声明并赋值）
class User {
    constructor(
        public name: string,
        private age: number,
        protected readonly role: string = 'user'
    ) {}
}

```

### 继承与抽象类

```ts
// 抽象类：不能直接实例化，用于定义基类契约
abstract class Shape {
    abstract getArea(): number          // 抽象方法：子类必须实现
    abstract getPerimeter(): number

    // 具体方法可以有实现
    describe(): string {
        return `Area: ${this.getArea()}, Perimeter: ${this.getPerimeter()}`
    }
}

class Circle extends Shape {
    constructor(private radius: number) {
        super()                          // 必须调用 super()
    }

    getArea(): number {
        return Math.PI * this.radius ** 2
    }

    getPerimeter(): number {
        return 2 * Math.PI * this.radius
    }
}

class Rectangle extends Shape {
    constructor(private width: number, private height: number) {
        super()
    }

    getArea(): number {
        return this.width * this.height
    }

    getPerimeter(): number {
        return 2 * (this.width + this.height)
    }
}

```

### 实现接口

```ts
interface Serializable {
    serialize(): string
    deserialize(data: string): this
}

interface Validatable {
    validate(): boolean
    errors: string[]
}

// 一个类可以实现多个接口
class UserModel implements Serializable, Validatable {
    errors: string[] = []

    constructor(public name: string, public email: string) {}

    serialize(): string {
        return JSON.stringify({ name: this.name, email: this.email })
    }

    deserialize(data: string): this {
        const parsed = JSON.parse(data)
        Object.assign(this, parsed)
        return this
    }

    validate(): boolean {
        this.errors = []
        if (!this.name) this.errors.push('Name is required')
        if (!this.email.includes('@')) this.errors.push('Invalid email')
        return this.errors.length === 0
    }
}

```

---

## 五、泛型

### 泛型函数

```ts
// 基础泛型
function identity<T>(value: T): T {
    return value
}

const str = identity<string>('hello')   // 显式指定
const num = identity(42)                // 自动推断为 number

// 多个类型参数
function pair<T, U>(first: T, second: U): [T, U] {
    return [first, second]
}

// 泛型约束
function getLength<T extends { length: number }>(arg: T): number {
    return arg.length
}

getLength('hello')        // 5
getLength([1, 2, 3])      // 3
getLength(42)             // Error: number 没有 length 属性

// keyof 约束
function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] {
    return obj[key]
}

const user = { name: 'Alice', age: 25 }
const name = getProperty(user, 'name')   // 类型为 string
const age = getProperty(user, 'age')     // 类型为 number
getProperty(user, 'email')               // Error: 'email' 不在 keyof 中

```

### 泛型接口与类

```ts
// 泛型接口
interface ApiResponse<T> {
    data: T
    status: number
    message: string
    timestamp: Date
}

// 泛型类
class Stack<T> {
    private items: T[] = []

    push(item: T): void {
        this.items.push(item)
    }

    pop(): T | undefined {
        return this.items.pop()
    }

    peek(): T | undefined {
        return this.items[this.items.length - 1]
    }

    get size(): number {
        return this.items.length
    }

    isEmpty(): boolean {
        return this.items.length === 0
    }
}

const numStack = new Stack<number>()
numStack.push(1)
numStack.push(2)

```

### 泛型默认值

```ts
// 泛型参数可以有默认值
interface Container<T = string> {
    value: T
    label: string
}

const c1: Container = { value: 'hello', label: 'text' }      // T 默认为 string
const c2: Container<number> = { value: 42, label: 'number' }  // 显式指定

```

### 条件类型中的泛型推断（infer）

```ts
// infer：在条件类型中推断类型
type UnpackPromise<T> = T extends Promise<infer U> ? U : T

type A = UnpackPromise<Promise<string>>   // string
type B = UnpackPromise<number>            // number

// 获取函数返回类型（内置 ReturnType 的实现原理）
type MyReturnType<T extends (...args: any) => any> =
    T extends (...args: any) => infer R ? R : never

// 获取数组元素类型
type ElementType<T> = T extends (infer U)[] ? U : never
type E = ElementType<string[]>   // string

```

---

## 六、高级类型

### 联合类型与交叉类型

```ts
// 联合类型：A 或 B
type StringOrNumber = string | number

// 判别联合类型（Discriminated Unions）—— 非常重要的模式
type Shape =
    | { kind: 'circle'; radius: number }
    | { kind: 'square'; side: number }
    | { kind: 'rectangle'; width: number; height: number }

function getArea(shape: Shape): number {
    switch (shape.kind) {
        case 'circle':
            return Math.PI * shape.radius ** 2   // shape 收窄为 circle
        case 'square':
            return shape.side ** 2               // shape 收窄为 square
        case 'rectangle':
            return shape.width * shape.height    // shape 收窄为 rectangle
    }
}

// 交叉类型：A 且 B
type WithTimestamp<T> = T & { createdAt: Date; updatedAt: Date }
type UserWithTimestamp = WithTimestamp<User>

```

### 映射类型

```ts
// 基础映射类型
type Readonly<T> = {
    readonly [K in keyof T]: T[K]
}

type Partial<T> = {
    [K in keyof T]?: T[K]
}

type Required<T> = {
    [K in keyof T]-?: T[K]    // -? 移除可选修饰符
}

// 自定义映射类型：让所有属性变为 nullable
type Nullable<T> = {
    [K in keyof T]: T[K] | null
}

// 键重映射（TypeScript 4.1+，使用 as 子句）
type Getters<T> = {
    [K in keyof T as `get${Capitalize<string & K>}`]: () => T[K]
}

interface User {
    name: string
    age: number
}

type UserGetters = Getters<User>
// { getName: () => string; getAge: () => number }

```

### 条件类型

```ts
// 基础条件类型
type IsArray<T> = T extends any[] ? true : false

type A = IsArray<string[]>   // true
type B = IsArray<string>     // false

// 分布式条件类型（当 T 为联合类型时，分别应用）
type ToArray<T> = T extends any ? T[] : never

type C = ToArray<string | number>   // string[] | number[]（分布式）

// 非分布式（用元组包裹）
type ToArrayNonDist<T> = [T] extends [any] ? T[] : never
type D = ToArrayNonDist<string | number>   // (string | number)[]

// 内置条件工具类型
type E = Exclude<'a' | 'b' | 'c', 'a'>          // 'b' | 'c'
type F = Extract<'a' | 'b' | 'c', 'a' | 'd'>    // 'a'
type G = NonNullable<string | null | undefined>  // string

```

### 索引访问类型

```ts
interface User {
    name: string
    address: {
        city: string
        country: string
    }
    tags: string[]
}

type NameType = User['name']              // string
type AddressType = User['address']        // { city: string; country: string }
type CityType = User['address']['city']   // string
type TagType = User['tags'][number]       // string（数组元素类型）

// 结合 keyof 使用
type UserValues = User[keyof User]        // string | { city: string; country: string } | string[]

```

### 模板字面量类型

```ts
type EventName = 'click' | 'focus' | 'blur'
type HandlerName = `on${Capitalize<EventName>}`
// 'onClick' | 'onFocus' | 'onBlur'

// 实际应用：CSS 属性值类型
type LengthUnit = 'px' | 'rem' | 'em' | '%' | 'vh' | 'vw'
type CSSLength = `${number}${LengthUnit}`

// 实际应用：API 路径类型
type Method = 'GET' | 'POST' | 'PUT' | 'DELETE'
type Endpoint = '/users' | '/posts' | '/comments'
type Route = `${Method} ${Endpoint}`
// 'GET /users' | 'GET /posts' | ... 所有组合

```

### 内置实用工具类型完整参考

```ts
interface User {
    id: number
    name: string
    email: string
    age?: number
    readonly createdAt: Date
}

```

| 工具类型                     | 语法                                    | 效果                |
| ------------------------ | ------------------------------------- | ----------------- |
| Partial<T>               | Partial<User>                         | 所有属性变可选           |
| Required<T>              | Required<User>                        | 所有属性变必填           |
| Readonly<T>              | Readonly<User>                        | 所有属性只读            |
| Pick<T, K>               | Pick<User, 'id' \| 'name'>            | 选取指定属性            |
| Omit<T, K>               | Omit<User, 'email'>                   | 排除指定属性            |
| Record<K, V>             | Record<string, User>                  | 构造键值类型            |
| Exclude<T, U>            | Exclude<string \| number, number>     | 联合中排除             |
| Extract<T, U>            | Extract<string \| number, number>     | 联合中提取             |
| NonNullable<T>           | NonNullable<string \| null>           | 排除 null/undefined |
| ReturnType<T>            | ReturnType<typeof fn>                 | 函数返回类型            |
| Parameters<T>            | Parameters<typeof fn>                 | 函数参数类型元组          |
| ConstructorParameters<T> | ConstructorParameters<typeof MyClass> | 构造函数参数类型          |
| InstanceType<T>          | InstanceType<typeof MyClass>          | 类的实例类型            |
| Awaited<T>               | Awaited<Promise<string>>              | 解包 Promise（4.5+）  |
| NoInfer<T>               | NoInfer<T>                            | 禁止从该位置推断（5.4+）    |

### 类型守卫

```ts
// typeof 守卫
function process(value: string | number): string {
    if (typeof value === 'string') {
        return value.toUpperCase()   // value: string
    }
    return value.toFixed(2)          // value: number
}

// instanceof 守卫
function handleError(err: unknown): string {
    if (err instanceof Error) {
        return err.message           // err: Error
    }
    return String(err)
}

// in 守卫（检查属性是否存在）
interface Cat { meow(): void }
interface Dog { bark(): void }

function makeSound(animal: Cat | Dog): void {
    if ('meow' in animal) {
        animal.meow()               // animal: Cat
    } else {
        animal.bark()               // animal: Dog
    }
}

// 自定义类型谓词（user-defined type guards）
function isUser(value: unknown): value is User {
    return (
        typeof value === 'object' &&
        value !== null &&
        'id' in value &&
        'name' in value
    )
}

// 断言函数（TypeScript 3.7+）
function assertIsString(value: unknown): asserts value is string {
    if (typeof value !== 'string') {
        throw new TypeError(`Expected string, got ${typeof value}`)
    }
}

// 使用
const input: unknown = getInput()
assertIsString(input)
console.log(input.toUpperCase())   // 这里 input 已被收窄为 string

```

---

## 七、模块与命名空间

### ES 模块（推荐）

```ts
// 导出
export interface User { id: number; name: string }
export type Status = 'active' | 'inactive'
export const DEFAULT_ROLE = 'user'
export function createUser(name: string): User { ... }
export class UserService { ... }

// 默认导出
export default class ApiClient { ... }

// 重新导出
export { createUser as makeUser } from './user'
export * from './helpers'
export * as utils from './utils'

// 导入
import ApiClient from './api-client'
import { User, createUser, DEFAULT_ROLE } from './user'
import type { User } from './user'   // 仅导入类型（不产生运行时代码）
import * as utils from './utils'

```

### import type 的重要性

```ts
// 普通 import：TypeScript 可能保留运行时导入
import { User } from './user'

// import type：编译后完全消除，确保不产生运行时依赖
import type { User } from './user'
import type { Ref, ComputedRef } from 'vue'

// 内联 type 导入（TypeScript 4.5+）
import { type User, createUser } from './user'

```

### 命名空间（namespace）

```ts
// 命名空间（主要用于组织声明文件，现代项目优先用模块）
namespace Validation {
    export interface StringValidator {
        isAcceptable(s: string): boolean
    }

    export class LettersOnlyValidator implements StringValidator {
        isAcceptable(s: string): boolean {
            return /^[A-Za-z]+$/.test(s)
        }
    }
}

const validator = new Validation.LettersOnlyValidator()

```

---

## 八、装饰器

装饰器目前处于 Stage 3 提案，TypeScript 5.0+ 已支持新版装饰器规范。

```ts
// tsconfig.json 中需要开启（新版不需要额外配置）
// "experimentalDecorators": true  // 旧版装饰器
// TypeScript 5.0+ 默认支持新版装饰器

// 类装饰器
function sealed(constructor: Function) {
    Object.seal(constructor)
    Object.seal(constructor.prototype)
}

@sealed
class BugReport {
    type = 'report'
    title: string
    constructor(t: string) { this.title = t }
}

// 方法装饰器
function log(target: any, key: string, descriptor: PropertyDescriptor) {
    const original = descriptor.value
    descriptor.value = function (...args: any[]) {
        console.log(`Calling ${key} with`, args)
        const result = original.apply(this, args)
        console.log(`Result:`, result)
        return result
    }
    return descriptor
}

class Calculator {
    @log
    add(a: number, b: number): number {
        return a + b
    }
}

// 属性装饰器
function required(target: any, key: string) {
    let value = target[key]
    Object.defineProperty(target, key, {
        get: () => value,
        set: (newValue) => {
            if (newValue === null || newValue === undefined) {
                throw new Error(`${key} is required`)
            }
            value = newValue
        }
    })
}

```

---

## 九、类型声明文件

### .d.ts 文件结构

```ts
// types/global.d.ts —— 全局类型扩展

// 扩展已有模块（模块增强）
declare module 'vue' {
    interface ComponentCustomProperties {
        $myPlugin: MyPlugin
        $formatDate: (date: Date) => string
    }
}

// 扩展全局对象
declare global {
    interface Window {
        analytics: Analytics
        gtag: (...args: any[]) => void
    }

    interface ImportMeta {
        env: {
            VITE_API_URL: string
            VITE_APP_TITLE: string
            MODE: string
            DEV: boolean
            PROD: boolean
        }
    }
}

export {}   // 使文件成为模块（否则 declare global 不起作用）

```

```ts
// 为 JS 文件编写类型声明
// mylib.d.ts

export declare function init(options: InitOptions): void
export declare function destroy(): void

export declare interface InitOptions {
    container: string | HTMLElement
    theme?: 'light' | 'dark'
    lang?: string
}

export declare class EventEmitter {
    on(event: string, listener: Function): this
    off(event: string, listener: Function): this
    emit(event: string, ...args: any[]): boolean
}

```

### 环境声明

```ts
// 声明未经过模块化的全局变量（如通过 CDN 加载的库）
declare const jQuery: JQueryStatic
declare function alert(message: string): void

// 声明模块（当某个包没有类型声明时的临时方案）
declare module 'untyped-package' {
    export function doSomething(input: string): void
    export default class Main { ... }
}

// 声明文件类型（用于 Vite 项目导入资源）
declare module '*.svg' {
    const content: string
    export default content
}

declare module '*.png' {
    const content: string
    export default content
}

declare module '*.vue' {
    import type { DefineComponent } from 'vue'
    const component: DefineComponent<{}, {}, any>
    export default component
}

```

---

## 十、tsconfig.json 完整配置

```json
{
  "compilerOptions": {
    // ============ 语言与环境 ============
    "target": "ES2022",
    // 编译输出的 JS 版本
    // ES3 / ES5 / ES6/ES2015 / ES2016~ES2022 / ESNext

    "lib": ["ES2022", "DOM", "DOM.Iterable"],
    // 包含的内置类型声明库
    // DOM: 浏览器 API（window/document 等）
    // ES2022: 最新 ES 特性类型

    "useDefineForClassFields": true,
    // 使用 Object.defineProperty 定义类字段（与现代浏览器一致）
    // Vue 3 + Vite 项目必须为 true

    // ============ 模块系统 ============
    "module": "ESNext",
    // 模块格式：CommonJS / ES6 / ESNext / NodeNext / Preserve

    "moduleResolution": "bundler",
    // 模块解析策略
    // node: 传统 Node.js CJS 解析
    // bundler: 现代打包工具（Vite/webpack），可省略扩展名
    // NodeNext: Node.js 原生 ESM，要求完整扩展名

    "baseUrl": ".",
    // 路径别名的基准目录

    "paths": {
      "@/*": ["src/*"],
      "@components/*": ["src/components/*"],
      "@utils/*": ["src/utils/*"]
    },
    // 路径别名（Vite 中还需在 vite.config.ts 同步配置 resolve.alias）

    "resolveJsonModule": true,
    // 允许导入 .json 文件

    "allowImportingTsExtensions": true,
    // 允许 import './foo.ts'（需要 noEmit 或 emitDeclarationOnly）

    // ============ 输出控制 ============
    "noEmit": true,
    // 不输出编译文件（Vite 项目中 TS 只做类型检查，由 Vite 负责编译）

    "outDir": "./dist",
    // 编译输出目录（noEmit 为 true 时无效）

    "rootDir": "./src",
    // 源码根目录

    "declaration": true,
    // 生成 .d.ts 类型声明文件（发布库时需要）

    "declarationDir": "./dist/types",
    // 声明文件输出目录

    "sourceMap": true,
    // 生成 .js.map 调试映射文件

    "removeComments": false,
    // 编译时是否移除注释

    // ============ 类型检查严格模式 ============
    "strict": true,
    // 开启所有严格检查（等价于以下全部开启）

    "strictNullChecks": true,
    // null/undefined 不能赋给其他类型（strict 包含）

    "noImplicitAny": true,
    // 禁止参数/变量隐式推断为 any（strict 包含）

    "strictFunctionTypes": true,
    // 函数参数逆变检查（strict 包含）

    "strictBindCallApply": true,
    // bind/call/apply 的参数类型检查（strict 包含）

    "strictPropertyInitialization": true,
    // 类属性必须在构造函数中初始化（strict 包含）

    "noImplicitThis": true,
    // 禁止 this 隐式为 any（strict 包含）

    "useUnknownInCatchVariables": true,
    // catch 变量类型为 unknown 而非 any（strict 包含）

    // ============ 额外检查 ============
    "noUnusedLocals": true,
    // 不允许未使用的局部变量

    "noUnusedParameters": true,
    // 不允许未使用的函数参数
    // 用 _param 前缀表示故意不使用

    "noImplicitReturns": true,
    // 函数所有代码路径必须有返回值

    "noFallthroughCasesInSwitch": true,
    // switch case 必须有 break 或 return

    "noUncheckedIndexedAccess": true,
    // 索引访问类型包含 undefined（arr[0] 类型为 T | undefined）

    "exactOptionalPropertyTypes": true,
    // 可选属性不能赋值为 undefined（必须省略该键）

    // ============ 互操作性 ============
    "esModuleInterop": true,
    // 允许 import CJS 模块（如 import fs from 'fs'）
    // 生成兼容 __esModule 标记的辅助代码

    "allowSyntheticDefaultImports": true,
    // 允许无默认导出的模块使用默认导入语法（esModuleInterop 自动开启）

    "forceConsistentCasingInFileNames": true,
    // 文件名大小写敏感（防止 macOS 不区分大小写导致的跨平台 bug）

    // ============ 跳过检查 ============
    "skipLibCheck": true
    // 跳过 .d.ts 文件的类型检查（加快编译，避免第三方库类型冲突）
  },

  "include": [
    "src/**/*.ts",
    "src/**/*.tsx",
    "src/**/*.vue",
    "vite.config.ts",
    "vitest.config.ts"
  ],

  "exclude": [
    "node_modules",
    "dist",
    "**/*.spec.ts",
    "**/*.test.ts"
  ]
}

```

### 多环境 tsconfig 分离

```
tsconfig.json              # 基础配置（被其他继承）
tsconfig.app.json          # 应用代码（src/）
tsconfig.node.json         # Node.js 配置（vite.config.ts 等）
tsconfig.vitest.json       # 测试配置

```

```json
// tsconfig.json（基础，不直接使用）
{
  "compilerOptions": {
    "target": "ES2022",
    "strict": true,
    "esModuleInterop": true,
    "skipLibCheck": true,
    "forceConsistentCasingInFileNames": true
  }
}

// tsconfig.app.json（应用代码）
{
  "extends": "./tsconfig.json",
  "compilerOptions": {
    "module": "ESNext",
    "moduleResolution": "bundler",
    "lib": ["ES2022", "DOM", "DOM.Iterable"],
    "noEmit": true,
    "useDefineForClassFields": true,
    "allowImportingTsExtensions": true,
    "paths": { "@/*": ["./src/*"] }
  },
  "include": ["src/**/*", "env.d.ts"]
}

// tsconfig.node.json（构建工具配置文件）
{
  "extends": "./tsconfig.json",
  "compilerOptions": {
    "module": "ESNext",
    "moduleResolution": "bundler",
    "lib": ["ES2022"],
    "noEmit": true
  },
  "include": ["vite.config.ts", "vitest.config.ts", "scripts/**/*"]
}

```

---

## 十一、Vue 3 + TypeScript 最佳实践

### 组件 Props 类型定义

```ts
<!-- UserCard.vue -->
<script setup lang="ts">
// 方式一：运行时声明（简单场景）
const props = defineProps({
  name: { type: String, required: true },
  age: { type: Number, default: 0 },
})

// 方式二：类型声明（推荐，更强的类型推断）
interface Props {
  name: string
  age?: number
  role: 'admin' | 'user' | 'guest'
  tags: string[]
  user: User
  onUpdate?: (user: User) => void
}

const props = defineProps<Props>()

// 方式三：类型声明 + 默认值（TypeScript 5.0+ / Vue 3.3+）
const props = withDefaults(defineProps<Props>(), {
  age: 0,
  role: 'user',
  tags: () => [],      // 数组/对象默认值必须用工厂函数
})
</script>

```

### Emits 类型定义

```ts
<script setup lang="ts">
// 方式一：运行时声明
const emit = defineEmits(['update', 'delete'])

// 方式二：类型声明（推荐）
const emit = defineEmits<{
  update: [user: User]              // Vue 3.3+ 命名元组语法
  delete: [id: number]
  'status-change': [status: Status]
}>()

// 旧语法（3.3 之前）
const emit = defineEmits<{
  (e: 'update', user: User): void
  (e: 'delete', id: number): void
}>()

// 使用
emit('update', updatedUser)
emit('delete', user.id)
</script>

```

### ref 与 reactive 类型

```ts
<script setup lang="ts">
import { ref, reactive, computed, watch } from 'vue'
import type { Ref, ComputedRef } from 'vue'

// ref：TypeScript 自动推断或手动指定
const count = ref(0)                        // Ref<number>
const name = ref<string>('')               // Ref<string>
const user = ref<User | null>(null)         // Ref<User | null>

// 手动声明类型（不推荐，冗余）
const count2: Ref<number> = ref(0)

// reactive：推断对象类型
const state = reactive({
  users: [] as User[],
  loading: false,
  currentPage: 1,
})

// 如需复杂接口
interface AppState {
  users: User[]
  loading: boolean
  currentPage: number
}
const state2 = reactive<AppState>({
  users: [],
  loading: false,
  currentPage: 1,
})

// computed：自动推断返回类型
const fullName = computed(() => `${state2.users[0]?.name ?? ''}`)
// 类型：ComputedRef<string>

// watch：参数类型
watch(
  () => state2.currentPage,
  (newPage: number, oldPage: number) => {
    fetchUsers(newPage)
  },
  { immediate: true, deep: false }
)
</script>

```

### 组合式函数（Composables）类型

```ts
// composables/useUser.ts
import { ref, computed } from 'vue'
import type { Ref, ComputedRef } from 'vue'

interface User {
  id: number
  name: string
  email: string
}

interface UseUserReturn {
  user: Ref<User | null>
  loading: Ref<boolean>
  error: Ref<string | null>
  isLoggedIn: ComputedRef<boolean>
  fetchUser: (id: number) => Promise<void>
  logout: () => void
}

export function useUser(): UseUserReturn {
  const user = ref<User | null>(null)
  const loading = ref(false)
  const error = ref<string | null>(null)

  const isLoggedIn = computed(() => user.value !== null)

  async function fetchUser(id: number): Promise<void> {
    loading.value = true
    error.value = null
    try {
      const res = await fetch(`/api/users/${id}`)
      if (!res.ok) throw new Error(`HTTP ${res.status}`)
      user.value = await res.json() as User
    } catch (e) {
      error.value = e instanceof Error ? e.message : 'Unknown error'
    } finally {
      loading.value = false
    }
  }

  function logout(): void {
    user.value = null
  }

  return { user, loading, error, isLoggedIn, fetchUser, logout }
}

```

### 模板 ref 类型

```ts
<script setup lang="ts">
import { ref, onMounted } from 'vue'

// DOM 元素 ref
const inputRef = ref<HTMLInputElement | null>(null)
const canvasRef = ref<HTMLCanvasElement | null>(null)

// 子组件 ref
import MyModal from './MyModal.vue'
const modalRef = ref<InstanceType<typeof MyModal> | null>(null)

onMounted(() => {
  inputRef.value?.focus()              // 可选链处理 null
  modalRef.value?.open()
})
</script>

<template>
  <input ref="inputRef" type="text" />
  <MyModal ref="modalRef" />
</template>

```

### Pinia Store 类型

```ts
// stores/user.ts
import { defineStore } from 'pinia'
import { ref, computed } from 'vue'

interface User {
  id: number
  name: string
  role: 'admin' | 'user'
}

// Setup Store（推荐，类型推断最好）
export const useUserStore = defineStore('user', () => {
  const currentUser = ref<User | null>(null)
  const token = ref<string>('')

  const isAdmin = computed(() => currentUser.value?.role === 'admin')
  const isLoggedIn = computed(() => !!token.value)

  async function login(credentials: { email: string; password: string }): Promise<void> {
    const res = await fetch('/api/auth/login', {
      method: 'POST',
      body: JSON.stringify(credentials),
    })
    const data = await res.json() as { user: User; token: string }
    currentUser.value = data.user
    token.value = data.token
  }

  function logout(): void {
    currentUser.value = null
    token.value = ''
  }

  return { currentUser, token, isAdmin, isLoggedIn, login, logout }
})

// 在组件中使用
import { storeToRefs } from 'pinia'
const store = useUserStore()
const { currentUser, isAdmin } = storeToRefs(store)  // 保持响应式
const { login, logout } = store                       // 方法直接解构

```

### Vue Router 类型

```ts
// router/index.ts
import { createRouter, createWebHistory } from 'vue-router'
import type { RouteRecordRaw } from 'vue-router'

const routes: RouteRecordRaw[] = [
  {
    path: '/',
    name: 'home',
    component: () => import('@/views/HomeView.vue'),
  },
  {
    path: '/user/:id',
    name: 'user-detail',
    component: () => import('@/views/UserDetail.vue'),
    meta: { requiresAuth: true },
  },
]

// 扩展路由 meta 类型
declare module 'vue-router' {
  interface RouteMeta {
    requiresAuth?: boolean
    title?: string
    roles?: string[]
  }
}

// 在组件中使用
import { useRoute, useRouter } from 'vue-router'

const route = useRoute()
const router = useRouter()

const userId = route.params.id as string
const query = route.query.search as string | undefined

router.push({ name: 'user-detail', params: { id: '123' } })

```

### API 请求类型封装

```ts
// utils/request.ts
interface RequestConfig {
  url: string
  method?: 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE'
  params?: Record<string, string | number | boolean>
  data?: unknown
  headers?: Record<string, string>
}

interface ApiResponse<T> {
  code: number
  data: T
  message: string
}

async function request<T>(config: RequestConfig): Promise<T> {
  const { url, method = 'GET', params, data, headers } = config

  const searchParams = params
    ? '?' + new URLSearchParams(
        Object.fromEntries(
          Object.entries(params).map(([k, v]) => [k, String(v)])
        )
      ).toString()
    : ''

  const res = await fetch(`/api${url}${searchParams}`, {
    method,
    headers: { 'Content-Type': 'application/json', ...headers },
    body: data ? JSON.stringify(data) : undefined,
  })

  if (!res.ok) throw new Error(`HTTP Error: ${res.status}`)

  const json = await res.json() as ApiResponse<T>
  if (json.code !== 0) throw new Error(json.message)

  return json.data
}

// API 模块
// api/user.ts
export interface User {
  id: number
  name: string
  email: string
}

export interface CreateUserDTO {
  name: string
  email: string
  password: string
}

export const userApi = {
  getList: (page = 1, size = 20) =>
    request<{ list: User[]; total: number }>({
      url: '/users',
      params: { page, size },
    }),

  getById: (id: number) =>
    request<User>({ url: `/users/${id}` }),

  create: (dto: CreateUserDTO) =>
    request<User>({ url: '/users', method: 'POST', data: dto }),

  update: (id: number, dto: Partial<CreateUserDTO>) =>
    request<User>({ url: `/users/${id}`, method: 'PUT', data: dto }),

  remove: (id: number) =>
    request<void>({ url: `/users/${id}`, method: 'DELETE' }),
}

```

---

## 十二、项目文件结构

### Vue 3 + TypeScript + Vite 推荐结构

```
my-project/
├── public/                         # 静态资源（不经过 Vite 处理）
│   └── favicon.ico
│
├── src/
│   ├── main.ts                     # 应用入口
│   ├── App.vue                     # 根组件
│   ├── env.d.ts                    # 环境变量和模块类型声明
│   │
│   ├── assets/                     # 静态资源（经过 Vite 处理）
│   │   ├── images/
│   │   └── styles/
│   │       ├── main.css
│   │       └── variables.css
│   │
│   ├── components/                 # 公共组件
│   │   ├── base/                   # 基础 UI 组件（Button、Input 等）
│   │   │   ├── BaseButton.vue
│   │   │   └── BaseInput.vue
│   │   ├── layout/                 # 布局组件
│   │   │   ├── AppHeader.vue
│   │   │   ├── AppSidebar.vue
│   │   │   └── AppFooter.vue
│   │   └── common/                 # 业务通用组件
│   │       ├── UserAvatar.vue
│   │       └── DataTable.vue
│   │
│   ├── views/                      # 页面级组件（路由直接映射）
│   │   ├── HomeView.vue
│   │   ├── user/
│   │   │   ├── UserListView.vue
│   │   │   └── UserDetailView.vue
│   │   └── auth/
│   │       ├── LoginView.vue
│   │       └── RegisterView.vue
│   │
│   ├── router/                     # 路由
│   │   ├── index.ts                # 路由实例创建
│   │   ├── guards.ts               # 路由守卫
│   │   └── routes/
│   │       ├── index.ts            # 汇总所有路由
│   │       ├── user.routes.ts
│   │       └── auth.routes.ts
│   │
│   ├── stores/                     # Pinia 状态管理
│   │   ├── index.ts                # 统一导出
│   │   ├── user.store.ts
│   │   └── app.store.ts
│   │
│   ├── composables/                # 组合式函数
│   │   ├── useUser.ts
│   │   ├── usePagination.ts
│   │   ├── useForm.ts
│   │   └── useRequest.ts
│   │
│   ├── api/                        # API 请求层
│   │   ├── request.ts              # axios/fetch 封装
│   │   ├── user.api.ts
│   │   └── auth.api.ts
│   │
│   ├── types/                      # 全局类型定义
│   │   ├── index.ts                # 统一导出
│   │   ├── user.types.ts
│   │   ├── api.types.ts            # 请求/响应通用类型
│   │   └── global.d.ts             # 全局声明扩展
│   │
│   ├── utils/                      # 工具函数
│   │   ├── index.ts
│   │   ├── date.ts
│   │   ├── format.ts
│   │   └── validate.ts
│   │
│   └── constants/                  # 常量
│       ├── index.ts
│       └── api.constants.ts
│
├── tests/                          # 测试
│   ├── unit/
│   └── e2e/
│
├── .eslintrc.cjs                   # ESLint 配置
├── .prettierrc                     # Prettier 配置
├── vite.config.ts                  # Vite 配置
├── vitest.config.ts                # 测试配置
├── tsconfig.json                   # TS 基础配置
├── tsconfig.app.json               # 应用代码 TS 配置
├── tsconfig.node.json              # 构建工具 TS 配置
└── package.json

```

### vite.config.ts 参考配置

```ts
import { fileURLToPath, URL } from 'node:url'
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'

export default defineConfig({
  plugins: [vue()],

  resolve: {
    alias: {
      '@': fileURLToPath(new URL('./src', import.meta.url)),
      '@components': fileURLToPath(new URL('./src/components', import.meta.url)),
      '@utils': fileURLToPath(new URL('./src/utils', import.meta.url)),
    },
  },

  server: {
    port: 5173,
    proxy: {
      '/api': {
        target: 'http://localhost:3000',
        changeOrigin: true,
        rewrite: (path) => path.replace(/^\/api/, ''),
      },
    },
  },

  build: {
    target: 'es2022',
    outDir: 'dist',
  },
})

```

### env.d.ts（环境变量类型）

```ts
/// <reference types="vite/client" />

// 扩展 ImportMeta.env 的类型
interface ImportMetaEnv {
  readonly VITE_API_BASE_URL: string
  readonly VITE_APP_TITLE: string
  readonly VITE_APP_VERSION: string
}

interface ImportMeta {
  readonly env: ImportMetaEnv
}

// 声明静态资源模块
declare module '*.vue' {
  import type { DefineComponent } from 'vue'
  const component: DefineComponent<{}, {}, any>
  export default component
}

declare module '*.svg' {
  const content: string
  export default content
}

declare module '*.png' {
  const content: string
  export default content
}

```

---

## 十三、最佳实践

### 1\. 严格模式全开，从不关闭

```json
// tsconfig.json
{
  "compilerOptions": {
    "strict": true   // 无论如何都开启
  }
}

```

### 2\. 优先使用 unknown 而非 any

```ts
// 不好：any 关闭了所有类型检查
function parse(data: any) {
  return data.name   // 无类型检查，运行时可能崩溃
}

// 好：unknown 使用前必须收窄
function parse(data: unknown): string {
  if (typeof data === 'object' && data !== null && 'name' in data) {
    return String((data as { name: unknown }).name)
  }
  throw new Error('Invalid data')
}

```

### 3\. 类型收窄而非断言

```ts
// 不好：强制断言，绕过类型系统
const name = (user as any).name

// 好：类型守卫，TypeScript 帮你验证
function getName(user: unknown): string {
  if (isUser(user)) return user.name
  throw new Error('Not a user')
}

```

### 4\. 使用 const 断言固化字面量

```ts
// 不好：类型为 string，失去精确信息
const ROLES = ['admin', 'user', 'guest']   // string[]

// 好：类型为 readonly ["admin", "user", "guest"]
const ROLES = ['admin', 'user', 'guest'] as const

type Role = typeof ROLES[number]   // 'admin' | 'user' | 'guest'

// 不好：config.method 类型为 string
const config = { method: 'GET', url: '/api' }

// 好：config.method 类型为 "GET"
const config = { method: 'GET', url: '/api' } as const

```

### 5\. 利用判别联合类型处理状态

```ts
// 不好：多个可选字段，状态不清晰
interface RequestState {
  loading: boolean
  data?: User
  error?: string
}

// 好：判别联合类型，状态互斥且明确
type RequestState =
  | { status: 'idle' }
  | { status: 'loading' }
  | { status: 'success'; data: User }
  | { status: 'error'; error: string }

// 使用时 TypeScript 帮你保证每种状态下的字段
function render(state: RequestState) {
  switch (state.status) {
    case 'loading':
      return '<Spinner />'
    case 'success':
      return state.data.name   // 类型安全，data 一定存在
    case 'error':
      return state.error       // 类型安全，error 一定存在
    default:
      return null
  }
}

```

### 6\. 使用 import type 减少运行时依赖

```ts
// 纯类型导入，编译后完全消除
import type { User, ApiResponse } from './types'
import type { Ref, ComputedRef } from 'vue'

// 混合导入（TypeScript 4.5+）
import { ref, type Ref } from 'vue'

```

### 7\. 为异步函数显式声明返回类型

```ts
// 不好：返回类型依赖推断，内部实现变化可能改变外部 API
async function getUser(id: number) {
  const res = await fetch(`/api/users/${id}`)
  return res.json()
}

// 好：明确契约，内部实现不影响类型签名
async function getUser(id: number): Promise<User> {
  const res = await fetch(`/api/users/${id}`)
  if (!res.ok) throw new Error(`HTTP ${res.status}`)
  return res.json() as Promise<User>
}

```

### 8\. 避免枚举，优先使用 const + 字面量类型

```ts
// 不推荐：enum 在运行时生成额外代码
enum Direction {
  Up = 'UP',
  Down = 'DOWN',
}

// 推荐：const 对象 + 类型提取（零运行时开销）
const Direction = {
  Up: 'UP',
  Down: 'DOWN',
} as const

type Direction = typeof Direction[keyof typeof Direction]
// 'UP' | 'DOWN'

```

### 9\. 工具函数使用泛型保持类型信息

```ts
// 不好：返回 any，类型信息丢失
function first(arr: any[]): any {
  return arr[0]
}

// 好：泛型保留类型
function first<T>(arr: T[]): T | undefined {
  return arr[0]
}

const name = first(['Alice', 'Bob'])   // 类型为 string | undefined
const num = first([1, 2, 3])           // 类型为 number | undefined

```

### 10\. 使用 satisfies 验证类型而不改变推断

```ts
// as 会改变推断类型（可能过于宽泛）
const config = {
  colors: { red: '#ff0000', green: '#00ff00' }
} as Record<string, { [key: string]: string }>

config.colors.red   // 类型为 string（正确）
config.colors.red.length  // 类型为 string（但 IDE 知道值）

// satisfies 验证类型但保留推断（TypeScript 4.9+）
const config2 = {
  colors: { red: '#ff0000', green: '#00ff00' }
} satisfies Record<string, Record<string, string>>

config2.colors.red         // 类型仍为 string
config2.colors.unknownKey  // Error：没有这个键

```

---

## 十四、常见陷阱与注意事项

### 1\. strictPropertyInitialization：类属性必须初始化

```ts
// 错误：启用 strict 后，未初始化会报错
class User {
  name: string   // Error: Property 'name' has no initializer
}

// 解决方案一：构造函数初始化
class User {
  name: string
  constructor(name: string) { this.name = name }
}

// 解决方案二：给默认值
class User {
  name: string = ''
}

// 解决方案三：可选属性
class User {
  name?: string
}

// 解决方案四：非空断言（确定会在构造后赋值时）
class User {
  name!: string   // ! 告诉 TS "我保证会赋值"
}

```

### 2\. 类型断言不是类型转换

```ts
// as 只是告诉 TypeScript 类型，不做运行时转换
const num = '42' as unknown as number
console.log(typeof num)   // 'string'（还是字符串！）
console.log(num + 1)      // '421'（字符串拼接）

// 真正的转换
const realNum = Number('42')

```

### 3\. 对象字面量的多余属性检查

```ts
interface User { name: string; age: number }

// 直接赋给有类型的变量：多余属性会报错
const u: User = { name: 'Alice', age: 25, email: 'a@b.com' }
// Error: Object literal may only specify known properties

// 中间变量赋值：不检查（新鲜度检查只在直接赋值时触发）
const obj = { name: 'Alice', age: 25, email: 'a@b.com' }
const u2: User = obj   // 正确（没有新鲜度检查）

```

### 4\. 联合类型中 never 的妙用：穷举检查

```ts
type Shape = 'circle' | 'square' | 'triangle'

function getArea(shape: Shape): number {
  switch (shape) {
    case 'circle': return 100
    case 'square': return 200
    // 忘记处理 triangle
    default:
      // 利用 never 做穷举检查：如果有未处理的分支，这里会报错
      const exhaustive: never = shape
      // Error: Type 'string' is not assignable to type 'never'
      throw new Error(`Unhandled shape: ${exhaustive}`)
  }
}

```

### 5\. 可选链与非空断言的区别

```ts
const user: User | null = getUser()

// 可选链：user 为 null/undefined 时返回 undefined
const name = user?.name              // string | undefined

// 非空断言：告诉 TS 绝对不为 null（如果是 null 则运行时报错）
const name2 = user!.name            // string（如果 user 为 null 则崩溃）

// 最安全：先检查
if (user) {
  const name3 = user.name           // string（TypeScript 确保）
}

```

### 6\. noUncheckedIndexedAccess 下的数组访问

```ts
// 开启 noUncheckedIndexedAccess 后
const arr: number[] = [1, 2, 3]
const first = arr[0]   // 类型为 number | undefined（不再是 number）

// 必须处理可能的 undefined
if (first !== undefined) {
  console.log(first.toFixed(2))
}

// 或用可选链
console.log(first?.toFixed(2))

```

### 7\. 泛型的默认推断行为

```ts
function createArray<T>(): T[] { return [] }

// 没有类型推断来源时必须显式指定
const arr1 = createArray()           // T 推断为 unknown
const arr2 = createArray<string>()   // 显式指定，正确

// 有推断来源时可以省略
function wrapInArray<T>(value: T): T[] { return [value] }
const arr3 = wrapInArray('hello')    // 推断为 string[]

```

### 8\. Vue 中的 defineProps 解构丢失响应式

```ts
// 错误：解构后 name 和 age 不再是响应式的
const { name, age } = defineProps<{ name: string; age: number }>()

// 正确方式一：通过 props 访问
const props = defineProps<{ name: string; age: number }>()
watch(() => props.name, ...)

// 正确方式二：Vue 3.5+ 支持响应式 props 解构
// 需要在 vite.config.ts 启用 propsDestructure 特性
const { name } = defineProps<{ name: string }>()
// 3.5+ 中这是响应式的

```

### 9\. 避免使用 Function 类型

```ts
// 不好：Function 类型太宽泛，丢失参数和返回类型信息
function call(fn: Function) { fn() }

// 好：明确函数签名
function call(fn: () => void) { fn() }
function callWith<T>(fn: (arg: T) => void, arg: T) { fn(arg) }

// 接受任意函数（用于高阶函数工具）
function memoize<T extends (...args: any[]) => any>(fn: T): T { ... }

```

### 10\. 类型导出的一致性

```ts
// 不好：类型和值混在一起导入
import { User, createUser, UserRole } from './user'
// 不清楚哪些是类型，哪些是值

// 好：类型单独用 import type
import type { User, UserRole } from './user'
import { createUser } from './user'

// 或混合语法（TypeScript 4.5+）
import { createUser, type User, type UserRole } from './user'

```

---

## 最佳实践

**优先用 `interface` 声明对象类型，用 `type` 声明联合类型和工具类型**：`interface` 支持声明合并（多处 `interface Foo` 会自动合并），适合描述对象形状和可扩展的 API；`type` 更适合联合类型、交叉类型、映射类型等高级类型操作。

```ts
// 推荐：对象形状用 interface
interface User { id: number; name: string }

// 推荐：联合类型用 type
type Status = 'pending' | 'active' | 'inactive'
type UserOrAdmin = User | Admin

```

**用 `satisfies` 运算符在保留字面量类型的同时进行类型校验**：`as` 断言会放弃类型检查；`satisfies` 既校验类型约束，又保留推断出的更精确类型（字面量类型）。

```ts
const config = {
  port: 3000,
  host: "localhost",
} satisfies Record<string, string | number>
// config.port 类型是 number（而非 string | number）

```

**善用内置工具类型**：`Partial<T>`、`Required<T>`、`Pick<T, K>`、`Omit<T, K>`、`Readonly<T>`、`Record<K, V>` 等工具类型可以组合出大多数常见类型变换，避免手写重复类型。

```ts
type CreateUserDto = Omit<User, 'id' | 'createdAt'>
type UpdateUserDto = Partial<CreateUserDto>

```

**设置 `tsconfig` 严格模式**：`"strict": true` 包含了 `strictNullChecks`、`noImplicitAny` 等多项检查，新项目从一开始就开启，旧项目逐步迁移。

**用 `import type` 明确导入类型，避免运行时副作用**：类型在编译后被擦除，用 `import type` 让打包工具知道这是仅类型导入，可以安全地做 tree-shaking。

---

## 常见陷阱

### 陷阱：`as` 类型断言绕过了类型检查

**现象：** 使用 `as` 强制断言后，代码运行时报属性不存在或类型错误，但编译期没有警告。

**原因：** `as` 告诉 TypeScript "相信我，它就是这个类型"，编译器放弃进一步检查。错误断言使类型安全失效。

**解决：** 用类型守卫（`typeof`、`instanceof`、用户定义类型谓词）替代 `as`；必须用 `as` 时先用 `unknown` 中转，减少意外断言风险。

```ts
// 危险：直接断言，类型系统失效
const user = response.data as User

// 安全：先验证再使用
function isUser(obj: unknown): obj is User {
  return typeof obj === 'object' && obj !== null && 'id' in obj
}
if (isUser(response.data)) {
  console.log(response.data.id)  // 类型安全
}

```

---

### 陷阱：`any` 类型感染使类型检查形同虚设

**现象：** 文件有很多类型注解，但 TypeScript 没有发现明显的类型错误，等到运行时才报错。

**原因：** 某个变量被标注或推断为 `any`，它的所有后续操作（访问属性、传参等）都失去类型检查，错误被隐藏。

**解决：** 开启 `"noImplicitAny": true`；用 `unknown` 替代 `any`（强制在使用前做类型收窄）。

```ts
// any：完全跳过检查
function parse(raw: any) { return raw.name }  // 无错误但不安全

// unknown：强制类型收窄
function parse(raw: unknown) {
  if (typeof raw === 'object' && raw !== null && 'name' in raw) {
    return (raw as { name: string }).name
  }
}

```

---

### 陷阱：泛型约束不当导致 "Type X is not assignable to type Y"

**现象：** 自定义泛型函数调用时报类型不兼容错误，但看起来类型是对的。

**原因：** 泛型约束（`extends`）定义不够精确，或函数返回类型与泛型参数不兼容。

**解决：** 明确泛型约束，用 `keyof T` 约束对象键，用条件类型精确推导返回值类型。

```ts
// 错误：约束不足
function getField<T>(obj: T, key: string): any { ... }

// 正确：用 keyof 约束键
function getField<T, K extends keyof T>(obj: T, key: K): T[K] {
  return obj[key]
}
const name = getField({ name: 'Alice', age: 25 }, 'name')  // string

```

---

## 参见

- [JavaScript入门](https://blog.vercanti.com/javascript-ru-men/)
- [Vue3入门](https://blog.vercanti.com/vue-3-ru-men-zhi-nan/)
- [React完全指南](https://blog.vercanti.com/react-wan-quan-zhi-nan/)
- [mypy类型系统完全指南](https://blog.vercanti.com/mypy-lei-xing-xi-tong-wan-quan-zhi-nan/)
- [JS模块系统](https://blog.vercanti.com/js-mjs-ts-wen-jian-qu-bie-yu-zui-jia-shi-jian/)