TypeScript的类型系统图灵完备,条件类型和映射类型是其最强大的特性。在前端开发实践中,善用高级类型可以在编译阶段捕获大量潜在Bug,减少运行时错误。本文通过实际工程场景,演示条件类型与映射类型的具体用法。
条件类型基础与分布式特性
条件类型的语法形如T extends U ? X : Y,根据类型关系选择不同的分支。其最实用的场景是类型提取和类型守卫。
// 基础条件类型:提取函数返回类型
type ReturnType<T> = T extends (...args: any[]) => infer R ? R : never;
// 提取Promise的包裹类型
type Awaited<T> = T extends Promise<infer U> ? U : T;
// 提取数组元素类型
type ElementOf<T> = T extends (infer E)[] ? E : never;
// 实际使用
type R1 = ReturnType<() => string>; // string
type R2 = Awaited<Promise<number>>; // number
type R3 = ElementOf<string[]>; // string
// 分布式条件类型:联合类型会分发
type ToArray<T> = T extends any ? T[] : never;
type R4 = ToArray<string | number>; // string[] | number[]
条件类型的分布式特性在处理联合类型时自动生效。如果需要禁用分发行为,用方括号包裹:[T] extends [U] ? X : Y。
映射类型:对象类型的批量转换
映射类型通过遍历键名集合,对每个属性应用变换。前端工程化中常用于生成派生类型。
// 将所有属性变为可选
type Partial<T> = {
[P in keyof T]?: T[P];
};
// 将所有属性变为只读
type Readonly<T> = {
readonly [P in keyof T]: T[P];
};
// 移除readonly修饰符
type Mutable<T> = {
-readonly [P in keyof T]: T[P];
};
// 将所有属性变为可为null
type Nullable<T> = {
[P in keyof T]: T[P] | null;
};
// 实际使用
interface User {
id: number;
name: string;
email: string;
}
type OptionalUser = Partial<User>;
type ReadonlyUser = Readonly<User>;
type NullableUser = Nullable<User>;
Key Remapping:键名重映射
TypeScript 4.1引入了键名重映射语法,允许在映射过程中修改属性名。跨端小程序开发中,常用于字段命名风格转换。
// 将snake_case转为camelCase
type SnakeToCamel<S extends string> =
S extends `${string}_${infer Tail}`
? `${Capitalize<SnakeToCamel<Tail>>}`
: S;
type CamelCase<T> = {
[K in keyof T as SnakeToCamel<K & string>]: T[K];
};
// API返回的字段是snake_case
interface ApiUser {
user_id: number;
user_name: string;
created_at: string;
is_active: boolean;
}
// 前端使用的camelCase类型
type FrontendUser = CamelCase<ApiUser>;
// { userId: number; userName: string; createdAt: string; isActive: boolean }
模板字面量类型与路由参数推导
模板字面量类型可以将字符串模式转换为精确的联合类型。在Web性能优化和路由系统中,这种特性可用于类型安全的路径定义。
// 从路由模式提取参数名
type ExtractParams<T extends string> =
T extends `${string}:${infer Param}/${infer Rest}`
? Param | ExtractParams<`/${Rest}`>
: T extends `${string}:${infer Param}`
? Param
: never;
// 路由路径定义
const routes = {
userDetail: '/users/:userId',
userPost: '/users/:userId/posts/:postId',
tagList: '/tags/:tag',
} as const;
// 自动推导路由参数类型
type RouteParams = {
[K in keyof typeof routes]: Record<ExtractParams<typeof routes[K]>, string>;
};
// { userDetail: Record<'userId', string>; ... }
类型安全的事件系统设计
组件库设计中,事件系统是最容易出错的模块。利用条件类型和映射类型,可以构建编译期安全的事件注册机制。
interface EventMap {
click: { x: number; y: number };
change: { value: string };
submit: { formData: Record<string, unknown> };
hover: void;
}
class TypedEventEmitter<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 {
if (!this.handlers[event]) {
this.handlers[event] = [];
}
this.handlers[event]!.push(handler);
}
off<K extends keyof T>(event: K, handler: (payload: T[K]) => void): void {
const list = this.handlers[event];
if (list) {
this.handlers[event] = list.filter(h => h !== handler);
}
}
emit<K extends keyof T>(event: K, payload: T[K]): void {
const list = this.handlers[event];
if (list) {
list.forEach(h => h(payload));
}
}
}
const emitter = new TypedEventEmitter<EventMap>();
emitter.on('click', (payload) => {
console.log(payload.x, payload.y); // 自动推导为 { x: number; y: number }
});
// emitter.emit('click', { value: 'test' }); // Error: 缺少 x, y
深层Partial与条件可选
响应式布局和表单场景中,经常需要深层可选类型。标准Partial只处理第一层属性,嵌套对象仍需保持原样。通过递归映射类型可以实现DeepPartial。
// 深层Partial
type DeepPartial<T> = {
[P in keyof T]?: T[P] extends object ? DeepPartial<T[P]> : T[P];
};
// 深层Readonly
type DeepReadonly<T> = {
readonly [P in keyof T]: T[P] extends object ? DeepReadonly<T[P]> : T[P];
};
// 条件可选:指定某些属性变为可选
type OptionalKeys<T, K extends keyof T> = Omit<T, K> & Partial<Pick<T, K>>;
interface Article {
id: number;
title: string;
content: string;
author: { id: number; name: string; avatar: string; };
tags: string[];
}
type ArticleUpdate = OptionalKeys<DeepPartial<Article>, 'id' | 'author'>;
类型推导在API层中的应用
前后端对接中,API响应类型同步是痛点。通过类型推导链路,可以从前端定义的请求函数自动推导响应类型,减少手动维护接口类型的工作量。
type ApiResponse<T> = { code: number; data: T; message: string };
function createApi<TPath extends string, TParams, TResponse>(
method: 'GET' | 'POST' | 'PUT' | 'DELETE',
path: TPath
) {
return async (params: TParams): Promise<TResponse> => {
const res = await fetch(path, {
method,
body: JSON.stringify(params),
headers: { 'Content-Type': 'application/json' }
});
const json: ApiResponse<TResponse> = await res.json();
return json.data;
};
}
const getUser = createApi<'/api/user', { id: number }, { id: number; name: string }>;
const user = await getUser({ id: 1 }); // user 自动推导为 { id: number; name: string }
TypeScript实战的核心在于用类型系统表达业务约束,将运行时错误前移到编译期。条件类型和映射类型提供了构建复杂类型逻辑的基础积木,组合使用可以覆盖绝大多数工程场景。Vue3生态和React框架中的组件Props定义、状态管理和路由参数,都可以通过这套类型工具实现类型安全。
原创文章,作者:小编,如若转载,请注明出处:https://www.yunthe.com/typescript-gao-ji-lei-xing-shi-zhan-tiao-jian-lei-xing-yu/