TypeScript 高级类型:从入门到精通
TypeScript 的类型系统是图灵完备的——这意味着你可以在类型层面做几乎任何计算。本文不是罗列 API,而是通过实际场景,展示如何用高级类型解决真实工程问题。
条件类型:类型层面的 if-else
基础语法
type IsString<T> = T extends string ? true : false
type A = IsString<'hello'> // true
type B = IsString<123> // false
type C = IsString<string | number> // boolean (分布式条件类型)
分布式条件类型
当 T 是联合类型时,条件类型会「分发」到每个成员:
type ToArray<T> = T extends any ? T[] : never
type Result = ToArray<string | number>
// string[] | number[]
// 阻止分发:用方括号包裹
type ToArrayNonDist<T> = [T] extends [any] ? T[] : never
type Result2 = ToArrayNonDist<string | number>
// (string | number)[]
实战:提取 API 响应类型
interface ApiResponses {
user: { data: User; error: null }
post: { data: Post; error: null }
error: { data: null; error: string }
}
// 提取成功的响应数据类型
type SuccessData<T> =
T extends { data: infer D; error: null } ? D : never
type UserOrPost = SuccessData<ApiResponses['user'] | ApiResponses['post'] | ApiResponses['error']>
// User | Post
映射类型:类型层面的 for-in
内置工具类型的本质
// Readonly 的实现
type MyReadonly<T> = {
readonly [P in keyof T]: T[P]
}
// Partial 的实现
type MyPartial<T> = {
[P in keyof T]?: T[P]
}
// Pick 的实现(结合条件类型)
type MyPick<T, K extends keyof T> = {
[P in K]: T[P]
}
实战:API 字段映射
场景:后端返回 snake_case,前端用 camelCase。需要一个类型工具自动映射。
type SnakeToCamel<S extends string> =
S extends `${infer Head}_${infer Tail}`
? `${Head}${Capitalize<SnakeToCamel<Tail>>}`
: S
type CamelToSnake<S extends string> =
S extends `${infer Head}${infer Tail}`
? `${Head extends Capitalize<Head> ? `_${Lowercase<Head>}` : Head}${CamelToSnake<Tail>}`
: S
// 自动转换整个对象的 key
type SnakeToCamelObj<T> = {
[K in keyof T as SnakeToCamel<K & string>]: T[K]
}
// 使用
interface BackendUser {
user_id: number
user_name: string
created_at: string
is_active: boolean
}
type FrontendUser = SnakeToCamelObj<BackendUser>
// {
// userId: number
// userName: string
// createdAt: string
// isActive: boolean
// }
修饰符的增删
// 移除 readonly
type Mutable<T> = {
-readonly [P in keyof T]: T[P]
}
// 移除可选
type Required<T> = {
[P in keyof T]-?: T[P]
}
// 将特定 key 变为可选
type Optional<T, K extends keyof T> = Omit<T, K> & Partial<Pick<T, K>>
interface User {
id: number
name: string
email: string
}
type UserWithOptionalEmail = Optional<User, 'email'>
// { id: number; name: string; email?: string }
infer:类型层面的变量声明
函数返回值提取
type MyReturnType<T> = T extends (...args: any[]) => infer R ? R : never
type MyParameters<T> = T extends (...args: infer P) => any ? P : never
type Awaited<T> = T extends Promise<infer U> ? U : T
// 嵌套 Promise 解包
type DeepAwaited<T> = T extends Promise<infer U> ? DeepAwaited<U> : T
type R1 = DeepAwaited<Promise<Promise<Promise<number>>>> // number
实战:提取数组元素类型
type ElementOf<T> = T extends (infer E)[] ? E : never
type R = ElementOf<['a', 'b', 'c']> // 'a' | 'b' | 'c'
type N = ElementOf<number[]> // number
实战:提取组件 Props 类型
import type { Component } from 'vue'
// 从 Vue 组件中提取 Props 类型
type ComponentProps<T> = T extends new () => { $props: infer P } ? P : never
// 从组件定义中提取
const MyComponent = defineComponent({
props: {
title: String,
count: { type: Number, required: true }
}
})
type MyProps = ComponentProps<typeof MyComponent>
模板字面量类型
基础用法
type Greeting = `Hello, ${string}!`
const msg: Greeting = 'Hello, World!' // ✅
const err: Greeting = 'Hi' // ❌
// 事件处理器名称
type Events = 'click' | 'hover' | 'focus'
type Handlers = `on${Capitalize<Events>}`
// "onClick" | "onHover" | "onFocus"
实战:类型安全的路由系统
interface RouteMap {
'/': { page: 'home' }
'/users': { page: 'users' }
'/users/:id': { page: 'user'; params: { id: string } }
'/posts/:id/comments': { page: 'comments'; params: { id: string } }
}
// 提取所有路由路径
type RoutePath = keyof RouteMap
// 提取带参数的路由路径
type DynamicRoute = {
[K in RoutePath]: K extends `${string}:${string}` ? K : never
}[RoutePath]
// "/users/:id" | "/posts/:id/comments"
// 参数提取
type ExtractParams<T extends string> =
T extends `${string}:${infer Param}/${infer Rest}`
? { [K in Param]: string } & ExtractParams<`/${Rest}`>
: T extends `${string}:${infer Param}`
? { [K in Param]: string }
: {}
type UserParams = ExtractParams<'/users/:id'>
// { id: string }
type CommentParams = ExtractParams<'/posts/:id/comments'>
// { id: string }
类型递归与深度操作
DeepPartial
type DeepPartial<T> = {
[P in keyof T]?: T[P] extends object ? DeepPartial<T[P]> : T[P]
}
interface Config {
app: {
name: string
version: string
}
server: {
port: number
host: string
}
}
type PartialConfig = DeepPartial<Config>
// 所有层级都变成可选
DeepReadonly
type DeepReadonly<T> = {
readonly [P in keyof T]: T[P] extends object ? DeepReadonly<T[P]> : T[P]
}
路径类型:类型安全的对象访问
type Path<T> = T extends object
? {
[K in keyof T & string]:
| K
| `${K}.${Path<T[K]> extends infer P ? P extends string ? P : never : never}`
}[keyof T & string]
: never
type ConfigPath = Path<Config>
// "app" | "server" | "app.name" | "app.version" | "server.port" | "server.host"
// 类型安全的 get 函数
function get<T, P extends Path<T>>(obj: T, path: P): unknown {
return path.split('.').reduce((acc: any, key) => acc?.[key], obj)
}
const config: Config = { /* ... */ }
const name = get(config, 'app.name') // ✅
// get(config, 'app.invalid') // ❌ 类型错误
类型守卫与自定义类型保护
类型谓词
function isString(value: unknown): value is string {
return typeof value === 'string'
}
function process(value: unknown) {
if (isString(value)) {
value.toUpperCase() // TS 知道这里 value 是 string
}
}
实战:Discriminated Union 守卫
type Result<T, E = Error> =
| { success: true; data: T }
| { success: false; error: E }
function isOk<T, E>(result: Result<T, E>): result is { success: true; data: T } {
return result.success
}
function handleResult(result: Result<string>) {
if (isOk(result)) {
console.log(result.data.toUpperCase()) // ✅ data 是 string
} else {
console.log(result.error.message) // ✅ error 是 Error
}
}
断言函数
function assertNonNull<T>(value: T): asserts value is NonNullable<T> {
if (value === null || value === undefined) {
throw new Error(`Expected non-null, got ${value}`)
}
}
const maybeUser: User | null = fetchUser()
assertNonNull(maybeUser)
// 此处 maybeUser 被推断为 User
实用类型工具集
// 联合类型转交叉类型
type UnionToIntersection<U> =
(U extends any ? (x: U) => void : never) extends (x: infer I) => void ? I : never
// 获取函数的第一个参数类型
type FirstArg<T> = T extends (first: infer F, ...rest: any[]) => any ? F : never
// 标记某些属性为必填
type RequireKeys<T, K extends keyof T> = Omit<T, K> & Required<Pick<T, K>>
// 标记某些属性为只读
type ReadonlyKeys<T, K extends keyof T> = Omit<T, K> & Readonly<Pick<T, K>>
// 非空数组
type NonEmptyArray<T> = [T, ...T[]]
// 固定长度的元组
type FixedLengthArray<T, N extends number, A extends T[] = []> =
A['length'] extends N ? A : FixedLengthArray<T, N, [...A, T]>
type Triple = FixedLengthArray<string, 3> // [string, string, string]
类型体操实战:类型安全的 EventEmitter
type EventMap = {
click: { x: number; y: number }
scroll: { top: number }
resize: { width: number; height: number }
}
class TypedEmitter<T extends Record<string, any>> {
private handlers: { [K in keyof T]?: Array<(payload: T[K]) => void> } = {}
on<K extends keyof T>(event: K, handler: (payload: T[K]) => void): void {
(this.handlers[event] ??= []).push(handler)
}
off<K extends keyof T>(event: K, handler: (payload: T[K]) => void): void {
this.handlers[event] = this.handlers[event]?.filter(h => h !== handler)
}
emit<K extends keyof T>(event: K, payload: T[K]): void {
this.handlers[event]?.forEach(h => h(payload))
}
}
const emitter = new TypedEmitter<EventMap>()
emitter.on('click', ({ x, y }) => console.log(x, y)) // ✅ 类型安全
emitter.emit('click', { x: 10, y: 20 }) // ✅
// emitter.emit('click', { x: 10 }) // ❌ 缺少 y
// emitter.on('unknown', () => {}) // ❌ 不存在的事件
总结
TypeScript 高级类型的核心思维是「类型也是程序」:
- 条件类型 是 if-else
- 映射类型 是 for-in
- infer 是变量声明
- 模板字面量类型 是字符串操作
- 递归类型 是循环
掌握了这些,你就能写出类型安全的路由系统、API 请求层、状态管理——把运行时错误在编译阶段消灭。建议从实际需求出发,每次遇到重复的类型逻辑,就尝试用高级类型来抽象。
Comments | 0条评论