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 | 接口不能定义基本类型 |
| 需要继承 | Interface | extends 语法更直观 |
| 工具类型 | 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 }
// }