TypeScript实战中,泛型约束是实现组件复用的核心机制。在大型前端项目中,合理使用泛型可以在编译阶段捕获类型错误,减少运行时异常。本文从泛型基础约束、条件类型、映射类型到实际业务场景,系统介绍TypeScript类型体操在企业项目中的应用技巧。
泛型基础与类型参数约束
泛型约束通过extends关键字限制类型参数的范围,确保传入类型满足特定条件。基本泛型函数约束:
// 约束T必须包含id属性
interface Identifiable {
id: string | number;
}
function findById<T extends Identifiable>(
items: T[],
id: string | number
): T | undefined {
return items.find(item => item.id === id);
}
// 使用
const users = [
{ id: 1, name: "张三", role: "admin" },
{ id: 2, name: "李四", role: "user" }
];
const user = findById(users, 1);
// 类型推导为 { id: number; name: string; role: string; } | undefined
keyof操作符配合泛型约束,实现类型安全的属性访问:
function pick<T, K extends keyof T>(obj: T, keys: K[]): Pick<T, K> {
const result = {} as Pick<T, K>;
keys.forEach(key => {
result[key] = obj[key];
});
return result;
}
const config = { host: "0.0.0.0", port: 8080, debug: true };
const partial = pick(config, ["host", "port"]);
// 类型为 { host: string; port: number; }
条件类型与分布式条件类型
条件类型允许根据输入类型动态决定输出类型,语法为T extends U ? X : Y。当T可赋值给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
分布式条件类型是容易踩坑的特性。当条件类型的检查对象是裸类型参数(naked type parameter)且传入联合类型时,条件类型会分布到联合类型的每个成员上:
// 裸类型参数 - 分布式
type ToArray<T> = T extends any ? T[] : never;
type R4 = ToArray<string | number>; // string[] | number[]
// 非裸类型参数(被包裹)- 不分布
type ToArrayNonDist<T> = [T] extends [any] ? T[] : never;
type R5 = ToArrayNonDist<string | number>; // (string | number)[]
映射类型与键重映射
映射类型基于已有类型生成新类型,通过in关键字遍历联合类型。TypeScript 4.1引入的键重映射(Key Remapping)语法使映射类型更加灵活。
// 将所有属性变为可选
type Optional<T> = {
[K in keyof T]?: T[K];
};
// 键重映射:添加前缀
type AddPrefix<T, P extends string> = {
[K in keyof T as `${P}${Capitalize<string & K>}`]: T[K];
};
interface UserProps {
name: string;
age: number;
}
type PrefixedUser = AddPrefix<UserProps, "user">;
// { userName: string; userAge: number; }
// 过滤特定类型的属性
type StringKeys<T> = {
[K in keyof T as T[K] extends string ? K : never]: T[K];
};
type R6 = StringKeys<{ name: string; age: number; email: string }>;
// { name: string; email: string; }
工具类型自定义实践
实际项目中,内置工具类型(Partial、Pick、Omit等)往往不够用。以下是一些常用自定义工具类型:
// 深层Partial
type DeepPartial<T> = {
[K in keyof T]?: T[K] extends object ? DeepPartial<T[K]> : T[K];
};
// 深层Readonly
type DeepReadonly<T> = {
readonly [K in keyof T]: T[K] extends object ? DeepReadonly<T[K]> : T[K];
};
// 获取可选属性键
type OptionalKeys<T> = {
[K in keyof T]-?: {} extends Pick<T, K> ? K : never;
}[keyof T];
// 获取必选属性键
type RequiredKeys<T> = {
[K in keyof T]-?: {} extends Pick<T, K> ? never : K;
}[keyof T];
// 联合类型转交叉类型
type UnionToIntersection<U> =
(U extends any ? (k: U) => void : never) extends ((k: infer I) => void) ? I : never;
实际业务场景应用
API响应类型推导是泛型的典型应用场景。通过定义统一的响应包装类型,可以确保所有API调用的类型安全:
interface ApiResponse<T> {
code: number;
message: string;
data: T;
}
interface PaginatedData<T> {
list: T[];
total: number;
page: number;
pageSize: number;
}
async function request<T>(url: string): Promise<T> {
const response = await fetch(url);
const result: ApiResponse<T> = await response.json();
if (result.code !== 0) {
throw new Error(result.message);
}
return result.data;
}
// 使用 - 类型自动推导
interface User { id: number; name: string; }
const user = await request<User>("/api/users/1");
// user 类型为 User
const usersPage = await request<PaginatedData<User>>("/api/users?page=1");
// usersPage.list 类型为 User[]
表单验证场景中,泛型约束可以确保验证规则与表单字段的类型一致性:
type Validator<T> = (value: T) => string | null;
function createForm<T extends Record<string, any>>(
initialValues: T,
validators: { [K in keyof T]?: Validator<T[K]> }
) {
let values = { ...initialValues };
const errors: Partial<Record<keyof T, string>> = {};
function validate(): boolean {
let valid = true;
(Object.keys(validators) as (keyof T)[]).forEach(key => {
const validator = validators[key];
if (validator) {
const error = validator(values[key]);
if (error) {
errors[key] = error;
valid = false;
}
}
});
return valid;
}
return { values, errors, validate };
}
// 使用 - 字段名和验证器类型自动约束
const form = createForm(
{ username: "", age: 0 },
{
username: (v) => v.length >= 3 ? null : "用户名至少3个字符",
age: (v) => v >= 18 ? null : "年龄必须大于18岁"
}
);
这种实现方式下,如果传入initialValues中不存在的字段验证器,或者验证器参数类型与字段类型不匹配,TypeScript编译阶段就会报错,无需等到运行时发现问题。Vue3组件库设计中,defineProps和defineEmits的泛型约束也是类似原理,通过类型推导实现编译时安全。
原创文章,作者:小编,如若转载,请注明出处:https://www.yunthe.com/typescript-fan-xing-yue-shu-shi-zhan-qi-ye-xiang-mu-zhong/