Skip to content

TypeScript 类型系统

大白话解释: TypeScript 的类型系统就像"交通规则"。没有规则(JS)时,车想怎么开就怎么开,容易出事故;有规则(TS)时,虽然限制多了一点,但更安全、更有序。

为什么要学类型系统?

  • 类型系统是 TypeScript 的核心,学好了才能真正发挥 TS 的威力
  • 理解类型系统,才能写出更好的类型定义
  • 遇到类型错误时,知道怎么解决

TypeScript 的核心是其强大的静态类型系统,提供类型检查、代码补全和文档功能。


类型注解和类型推断

类型注解

ts
// ---- 类型注解:手动告诉 TS 变量是什么类型 ----
// 大白话:就像"贴标签",明确告诉 TS 这个变量是什么类型

// 变量类型注解
let name: string = '张三'
let age: number = 25
let isActive: boolean = true

// 函数参数和返回值类型注解
function add(a: number, b: number): number {
  return a + b
}

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

// 对象类型注解
let user: { name: string; age: number } = {
  name: '张三',
  age: 25,
}

// 数组类型注解
let numbers: number[] = [1, 2, 3]
let strings: Array<string> = ['a', 'b', 'c']

类型推断

ts
// ---- 类型推断:TS 自动猜变量是什么类型 ----
// 大白话:就像"读心术",TS 根据赋的值自动推断类型

// TS 可以自动推断类型
let name = '张三'  // 推断为 string
let age = 25       // 推断为 number
let isActive = true // 推断为 boolean

// 函数返回值类型推断
function add(a: number, b: number) {
  return a + b  // 推断返回类型为 number
}

// 最佳实践:能推断就省略类型注解
const user = {
  name: '张三',
  age: 25,
}  // 推断为 { name: string; age: number }

// 复杂类型推断
const numbers = [1, 2, 3]  // 推断为 number[]
const mixed = [1, 'two', true]  // 推断为 (string | number | boolean)[]

// 函数类型推断
const add = (a: number, b: number) => a + b  // 推断为 (a: number, b: number) => number

类型断言

ts
// ---- 类型断言:告诉 TS "我比你更清楚这个类型" ----
// 大白话:就像"强行指定",你确定是什么类型就告诉 TS

// as 语法(推荐)
const value1: unknown = 'hello'
const len1 = (value1 as string).length  // ✅ 告诉 TS 这是 string

// 尖括号语法(在 JSX 中不能用)
const value2: unknown = 'hello'
const len2 = (<string>value2).length  // ✅

// 双重断言:极端情况,尽量少用
const value3: string = 'hello'
const num = (value3 as unknown as number)  // string → unknown → number

// const 断言:让 TS 推断为最具体的类型
const obj = { name: '张三', age: 25 } as const
// 类型变为 { readonly name: '张三'; readonly age: 25 }

const arr = [1, 2, 3] as const
// 类型变为 readonly [1, 2, 3]

// 实际用法:DOM 操作
const input = document.getElementById('myInput') as HTMLInputElement
input.value = 'hello'  // ✅ 可以访问 HTMLInputElement 的属性

// 实际用法:类型收窄后的断言
function process(value: string | number) {
  if (typeof value === 'string') {
    // 这里 value 已经是 string,不需要断言
    console.log(value.toUpperCase())
  }
}

联合类型和交叉类型

联合类型

ts
// ---- 联合类型:值可以是多种类型之一 ----
// 大白话:就像"多选一",变量可以是 A 类型或 B 类型

// 基本联合类型
let id: string | number
id = 123    // ✅
id = 'abc'  // ✅
// id = true  // ❌ 不是 string 或 number

// 函数参数使用联合类型
function formatId(id: string | number): string {
  // 使用前需要判断类型
  if (typeof id === 'string') {
    return id.toUpperCase()  // 这里 id 是 string
  } else {
    return id.toString()     // 这里 id 是 number
  }
}

// 字面量联合类型:限定具体的值
type Direction = 'up' | 'down' | 'left' | 'right'
type Status = 0 | 1 | 2 | 3
type HttpMethod = 'GET' | 'POST' | 'PUT' | 'DELETE'

// 使用
let direction: Direction = 'up'
// direction = 'forward'  // ❌ 不在联合类型中

// 实际用法:API 响应状态
type ApiResponse<T> = 
  | { status: 'loading' }
  | { status: 'success'; data: T }
  | { status: 'error'; message: string }

function handleResponse<T>(response: ApiResponse<T>) {
  switch (response.status) {
    case 'loading':
      console.log('加载中...')
      break
    case 'success':
      console.log('数据:', response.data)
      break
    case 'error':
      console.log('错误:', response.message)
      break
  }
}

交叉类型

ts
// ---- 交叉类型:合并多个类型 ----
// 大白话:就像"合体",把多个类型的属性都合并到一起

// 基本交叉类型
interface HasName {
  name: string
}

interface HasAge {
  age: number
}

type Person = HasName & HasAge

const person: Person = {
  name: '张三',
  age: 25,
  // 必须同时有 name 和 age
}

// 合并接口
interface User {
  id: number
  name: string
}

interface Admin {
  permissions: string[]
}

type AdminUser = User & Admin

const admin: AdminUser = {
  id: 1,
  name: '管理员',
  permissions: ['read', 'write', 'delete'],
}

// 实际用法:扩展已有类型
type WithTimestamp<T> = T & {
  createdAt: Date
  updatedAt: Date
}

type UserWithTimestamp = WithTimestamp<User>
// { id: number; name: string; createdAt: Date; updatedAt: Date }

类型别名和接口

类型别名(Type)

ts
// ---- 类型别名:给类型起个名字 ----
// 大白话:就像给复杂的类型取个"外号"

// 基本类型别名
type ID = string | number
type Callback = (data: string) => void
type Status = 'active' | 'inactive' | 'pending'

// 对象类型别名
type User = {
  id: ID
  name: string
  email?: string
  readonly createdAt: Date
}

// 函数类型别名
type SearchFunc = (keyword: string, page: number) => Promise<User[]>

// 映射类型(高级用法)
// 大白话:遍历对象的所有属性,给每个属性"加标签"

// Readonly<T>:把 T 的所有属性变成只读
// 解读:[P in keyof T] 遍历 T 的每个属性名 P,T[P] 是属性类型
type Readonly<T> = {
  readonly [P in keyof T]: T[P]    // 给每个属性加 readonly 标签
}

// Partial<T>:把 T 的所有属性变成可选
// 解读:加 ? 表示属性可以不传
type Partial<T> = {
  [P in keyof T]?: T[P]            // 给每个属性加 ? 标签
}

// 使用
type ReadonlyUser = Readonly<User>   // { readonly id: number; readonly name: string; ... }
type PartialUser = Partial<User>     // { id?: number; name?: string; ... }

接口(Interface)

ts
// ---- 接口:定义对象的"形状" ----
// 大白话:就像"合同模板"

// 基本接口
interface User {
  id: number
  name: string
  email?: string
  readonly createdAt: Date
}

// 接口继承
interface Admin extends User {
  permissions: string[]
}

// 函数接口
interface SearchFunc {
  (keyword: string, page: number): Promise<User[]>
}

// 索引签名
interface Dictionary {
  [key: string]: string
}

// 声明合并(接口特有)
interface User {
  nickname: string  // 合并到之前的 User 接口
}

// 现在 User 有 id, name, email, createdAt, nickname

选择建议

场景推荐原因
定义对象结构Interface支持声明合并,性能更好
联合/交叉类型Type接口不支持联合类型
基本类型别名Type接口不能定义基本类型
需要继承Interfaceextends 语法更直观
工具类型Type支持条件类型和映射类型

字面量类型

字符串字面量

ts
// ---- 字符串字面量:限定具体的字符串值 ----
// 大白话:就像"菜单选项",只能选菜单上有的

// 字符串字面量类型
type Theme = 'light' | 'dark'
type HttpMethod = 'GET' | 'POST' | 'PUT' | 'DELETE'

// 使用
const theme: Theme = 'light'
const method: HttpMethod = 'GET'

// 函数参数使用字面量类型
function setTheme(theme: Theme): void {
  document.body.className = theme
}

setTheme('dark')    // ✅
// setTheme('blue')  // ❌ 类型错误

// 实际用法:配置选项
interface Config {
  theme: 'light' | 'dark'
  size: 'small' | 'medium' | 'large'
  position: 'top' | 'bottom' | 'left' | 'right'
}

const config: Config = {
  theme: 'dark',
  size: 'medium',
  position: 'top',
}

数字字面量

ts
// ---- 数字字面量:限定具体的数字值 ----
// 大白话:就像"骰子点数",只能是 1-6

type DiceRoll = 1 | 2 | 3 | 4 | 5 | 6
type HttpStatus = 200 | 301 | 404 | 500

const roll: DiceRoll = 3
const status: HttpStatus = 404

// 实际用法:错误码
type ErrorCode = 
  | 10001  // 参数错误
  | 10002  // 未授权
  | 10003  // 禁止访问
  | 20001  // 服务器错误

function handleError(code: ErrorCode) {
  switch (code) {
    case 10001:
      console.log('参数错误')
      break
    case 10002:
      console.log('未授权')
      break
    // ...
  }
}

模板字面量类型

ts
// ---- 模板字面量类型:字符串模板的类型版本 ----
// 大白话:就像"字符串拼接的类型版",可以动态生成字符串类型

// 基本模板字面量
type EventName = `on${string}`  // 以 "on" 开头的字符串
type CSSProperty = `margin-${'top' | 'right' | 'bottom' | 'left'}`

// 组合
type Color = 'red' | 'blue' | 'green'
type Shade = 'light' | 'dark'
type ColorVariant = `${Shade}-${Color}`  
// 'light-red' | 'light-blue' | 'light-green' | 'dark-red' | 'dark-blue' | 'dark-green'

// 实际用法:事件名称
type ButtonEvent = `onClick` | `onHover` | `onFocus`
type InputEvent = `onInput` | `onChange` | `onBlur`

// 自动生成 getter 类型
type Getters<T> = {
  [P in keyof T as `get${Capitalize<string & P>}`]: () => T[P]
}

interface User {
  name: string
  age: number
}

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

类型守卫和类型收窄

typeof 守卫

ts
// ---- typeof 守卫:检查基本类型 ----
// 大白话:就像"安检门",检查是什么类型

function format(value: string | number): string {
  if (typeof value === 'string') {
    return value.toUpperCase()  // 这里 TS 知道 value 是 string
  } else {
    return value.toFixed(2)     // 这里 TS 知道 value 是 number
  }
}

// 检查多种类型
function process(value: string | number | boolean) {
  if (typeof value === 'string') {
    console.log('字符串:', value.toUpperCase())
  } else if (typeof value === 'number') {
    console.log('数字:', value.toFixed(2))
  } else {
    console.log('布尔:', value)
  }
}

instanceof 守卫

ts
// ---- instanceof 守卫:检查类实例 ----
// 大白话:就像"检查身份证",看是不是某个类的实例

function formatDate(value: string | Date): string {
  if (value instanceof Date) {
    return value.toISOString()  // 这里 TS 知道 value 是 Date
  } else {
    return new Date(value).toISOString()  // 这里 TS 知道 value 是 string
  }
}

// 实际用法:处理不同的错误类型
class ValidationError extends Error {
  constructor(public field: string, message: string) {
    super(message)
  }
}

class NetworkError extends Error {
  constructor(public statusCode: number, message: string) {
    super(message)
  }
}

function handleError(error: Error) {
  if (error instanceof ValidationError) {
    console.log(`字段 ${error.field} 验证失败:${error.message}`)
  } else if (error instanceof NetworkError) {
    console.log(`网络错误 ${error.statusCode}:${error.message}`)
  } else {
    console.log('未知错误:', error.message)
  }
}

in 守卫

ts
// ---- in 守卫:检查属性是否存在 ----
// 大白话:就像"检查口袋里有没有钥匙"

interface Circle {
  kind: 'circle'
  radius: number
}

interface Square {
  kind: 'square'
  side: number
}

type Shape = Circle | Square

function getArea(shape: Shape): number {
  if ('radius' in shape) {
    return Math.PI * shape.radius ** 2  // 这里 TS 知道 shape 是 Circle
  } else {
    return shape.side ** 2  // 这里 TS 知道 shape 是 Square
  }
}

自定义类型守卫

ts
// ---- 自定义类型守卫:自己写检查函数 ----
// 大白话:就像"专业鉴定师",自己写逻辑判断是什么类型

interface Cat {
  meow(): void
}

interface Dog {
  bark(): void
}

// 返回类型是 "animal is Cat",告诉 TS 如果返回 true,就是 Cat
function isCat(animal: Cat | Dog): animal is Cat {
  return (animal as Cat).meow !== undefined
}

function makeSound(animal: Cat | Dog): void {
  if (isCat(animal)) {
    animal.meow()  // 这里 TS 知道 animal 是 Cat
  } else {
    animal.bark()  // 这里 TS 知道 animal 是 Dog
  }
}

// 实际用法:检查 API 响应
interface SuccessResponse {
  data: any
}

interface ErrorResponse {
  error: string
}

function isSuccess(response: SuccessResponse | ErrorResponse): response is SuccessResponse {
  return 'data' in response
}

function handleResponse(response: SuccessResponse | ErrorResponse) {
  if (isSuccess(response)) {
    console.log('成功:', response.data)
  } else {
    console.log('失败:', response.error)
  }
}

可辨识联合

ts
// ---- 可辨识联合:用字面量类型区分不同类型 ----
// 大白话:就像"快递单上的类型栏"

interface Circle {
  kind: 'circle'
  radius: number
}

interface Square {
  kind: 'square'
  side: number
}

interface Triangle {
  kind: 'triangle'
  base: number
  height: number
}

type Shape = Circle | Square | Triangle

// 穷尽检查:确保处理了所有可能的类型
function getArea(shape: Shape): number {
  switch (shape.kind) {
    case 'circle':
      return Math.PI * shape.radius ** 2
    case 'square':
      return shape.side ** 2
    case 'triangle':
      return (shape.base * shape.height) / 2
    default:
      // 如果遗漏了某个 case,这里会报错
      const _exhaustive: never = shape
      return _exhaustive
  }
}

索引类型和映射类型

索引类型

ts
// ---- 索引类型:动态访问对象的属性 ----
// 大白话:就像"按名字找人",用字符串作为 key 访问属性

// 索引签名
interface Dictionary<T> {
  [key: string]: T
}

const dict: Dictionary<number> = {
  a: 1,
  b: 2,
}

// keyof 操作符:获取对象的所有 key
interface User {
  id: number
  name: string
  email: string
}

type UserKeys = keyof User  // 'id' | 'name' | 'email'

// 索引访问类型:获取对象某个 key 的类型
type UserName = User['name']  // string
type UserId = User['id']      // number

// 实际用法:安全地访问对象属性
function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] {
  return obj[key]
}

const user: User = { id: 1, name: '张三', email: '[email protected]' }
const name = getProperty(user, 'name')  // string
// const age = getProperty(user, 'age')  // ❌ 'age' 不是 User 的 key

映射类型

ts
// ---- 映射类型:遍历对象的 key,生成新的类型 ----
// 大白话:就像"批量加工",把对象的每个属性都做同样的变换

// 基本映射类型
type Readonly<T> = {
  readonly [P in keyof T]: T[P]
}

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

// 使用
type ReadonlyUser = Readonly<User>
type PartialUser = Partial<User>

// 自定义映射类型
type Nullable<T> = {
  [P in keyof T]: T[P] | null
}

// 生成 getter 类型
// 解读:
// 1. [P in keyof T]:遍历 T 的每个属性名(如 'name', 'age')
// 2. as `get${Capitalize<string & P>}`:把属性名转成 getter 名('name' → 'getName')
// 3. (): () => T[P]:每个 getter 是一个返回对应类型值的函数
type Getters<T> = {
  [P in keyof T as `get${Capitalize<string & P>}`]: () => T[P]
}

type UserGetters = Getters<User>
// {
//   getId: () => number      ← 'id' → 'getId',返回 number
//   getName: () => string    ← 'name' → 'getName',返回 string
//   getEmail: () => string   ← 'email' → 'getEmail',返回 string
// }

// 实际用法:表单状态
type FormState<T> = {
  [P in keyof T]: {
    value: T[P]
    error: string | null
    touched: boolean
  }
}

interface LoginForm {
  username: string
  password: string
}

type LoginFormState = FormState<LoginForm>
// {
//   username: { value: string; error: string | null; touched: boolean }
//   password: { value: string; error: string | null; touched: boolean }
// }

条件类型

基本条件类型

ts
// ---- 条件类型:根据条件决定类型 ----
// 大白话:就像"三元运算符",但用在类型上

// 条件类型语法
type IsString<T> = T extends string ? true : false

type A = IsString<string>  // true
type B = IsString<number>  // false

// 实际应用
type NonNullable<T> = T extends null | undefined ? never : T

type C = NonNullable<string | null | undefined>  // string

// 更复杂的条件类型
type IsArray<T> = T extends any[] ? true : false
type D = IsArray<string[]>  // true
type E = IsArray<string>    // false

分布式条件类型

ts
// ---- 分布式条件类型:联合类型的每个成员都应用条件 ----
// 大白话:就像"流水线",每个类型都过一遍条件检查
// 关键:当 T 是联合类型(A | B)时,会拆开分别计算,再合并结果

type ToArray<T> = T extends any ? T[] : never

// 当 T = string | number 时,TS 会自动拆开:
// 第1步:ToArray<string> → string[]
// 第2步:ToArray<number> → number[]
// 第3步:合并结果 → string[] | number[]
type F = ToArray<string | number>  // string[] | number[]

// 实际用法:过滤类型
// Exclude<T, U>:从 T 中排除能赋值给 U 的类型
// 大白话:T 是"所有选项",U 是"要排除的选项"
type Exclude<T, U> = T extends U ? never : T  // 匹配的返回 never(排除),不匹配的保留

// Extract<T, U>:从 T 中提取能赋值给 U 的类型
// 大白话:T 是"所有选项",U 是"要保留的选项"
type Extract<T, U> = T extends U ? T : never  // 匹配的保留,不匹配的返回 never(排除)

type Status = 'active' | 'inactive' | 'pending'

// 排除 'inactive' 和 'pending',只留 'active'
type ActiveStatus = Exclude<Status, 'inactive' | 'pending'>  // 'active'

// 只提取 'inactive' 和 'pending'
type InactiveStatus = Extract<Status, 'inactive' | 'pending'>  // 'inactive' | 'pending'

infer 关键字

ts
// ---- infer:在条件类型中"捕获"类型 ----
// 大白话:就像"抓娃娃机",从类型中抓出你想要的部分

// 提取函数返回类型
type ReturnType<T> = T extends (...args: any[]) => infer R ? R : never

type G = ReturnType<() => string>  // string
type H = ReturnType<(x: number) => boolean>  // boolean

// 提取函数参数类型
type Parameters<T> = T extends (...args: infer P) => any ? P : never

type I = Parameters<(a: string, b: number) => void>  // [a: string, b: number]

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

type J = ElementType<string[]>  // string
type K = ElementType<number[]>  // number

// 提取 Promise 的值类型(递归处理嵌套 Promise)
type Awaited<T> = T extends Promise<infer U> ? Awaited<U> : T

type L = Awaited<Promise<string>>  // string
type M = Awaited<Promise<number>>  // number
type N = Awaited<Promise<Promise<string>>>  // string(递归展开嵌套 Promise)

// 实际用法:提取 Vue 组件的 props 类型
type PropsOf<C> = C extends { new (...args: any[]): { $props: infer P } } ? P : never

内置工具类型

详细内容请查看 TypeScript 泛型 - 泛型工具类型

ts
// ---- 常用工具类型速查 ----
// 大白话:就像"瑞士军刀",常用的类型变换工具都有了

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

// Partial<T>:所有属性变为可选(适合更新操作)
type PartialUser = Partial<User>
// { id?: number; name?: string; email?: string }

// Required<T>:所有属性变为必需
type RequiredUser = Required<User>
// { id: number; name: string; email: string }

// Readonly<T>:所有属性变为只读
type ReadonlyUser = Readonly<User>
// { readonly id: number; readonly name: string; readonly email?: string }

// Pick<T, K>:选取部分属性
type UserBasic = Pick<User, 'id' | 'name'>
// { id: number; name: string }

// Omit<T, K>:排除部分属性
type UserWithoutEmail = Omit<User, 'email'>
// { id: number; name: string }

// Record<K, V>:构造键值对类型
type UserMap = Record<string, User>
// { [key: string]: User }

// Exclude<T, U>:从联合类型中排除
type Status = 'active' | 'inactive' | 'pending'
type ActiveStatus = Exclude<Status, 'inactive' | 'pending'>  // 'active'

// Extract<T, U>:从联合类型中提取
type InactiveStatus = Extract<Status, 'inactive' | 'pending'>  // 'inactive' | 'pending'

// NonNullable<T>:排除 null 和 undefined
type MaybeString = string | null | undefined
type DefinitelyString = NonNullable<MaybeString>  // string

参考


Vue/Vite 项目实际示例

组件 Props 类型定义

ts
// ---- Vue 组件 Props 类型 ----
// 使用 interface 定义 Props 结构,方便复用和继承

interface BaseProps {
  id: number
  name: string
}

// 用交叉类型扩展 Props
type UserCardProps = BaseProps & {
  email: string
  role: 'admin' | 'user' | 'guest'
  avatar?: string  // 可选属性
}

// 在 Vue 组件中使用
// <script setup lang="ts">
// const props = defineProps<UserCardProps>()
// </script>

API 响应类型

ts
// ---- API 响应类型 ----
// 使用泛型 + 联合类型定义不同状态的响应

type ApiResponse<T> =
  | { status: 'loading' }                    // 加载中,没有数据
  | { status: 'success'; data: T }           // 成功,有数据
  | { status: 'error'; message: string }     // 失败,有错误信息

// 使用条件类型提取数据
type ExtractData<T> = T extends { status: 'success'; data: infer D } ? D : never

interface User {
  id: number
  name: string
}

// 成功时 data 是 User 类型
const response: ApiResponse<User> = {
  status: 'success',
  data: { id: 1, name: '张三' }
}

表单状态类型

ts
// ---- 表单状态类型 ----
// 使用映射类型把表单字段转成带状态的对象

type FormState<T> = {
  [P in keyof T]: {
    value: T[P]               // 字段的值
    error: string | null      // 错误信息,null 表示无错误
    touched: boolean          // 是否被用户操作过
    dirty: boolean            // 值是否被修改过
  }
}

interface LoginForm {
  username: string
  password: string
  remember: boolean
}

// 自动生成表单状态类型
type LoginFormState = FormState<LoginForm>
// {
//   username: { value: string; error: string | null; touched: boolean; dirty: boolean }
//   password: { value: string; error: string | null; touched: boolean; dirty: boolean }
//   remember: { value: boolean; error: string | null; touched: boolean; dirty: boolean }
// }

个人学习笔记,部分内容借助 AI 辅助整理,仅供查阅参考,请以官方文档为准