TypeScript实战中,泛型和条件类型是类型系统最强大的特性,也是前端工程化中类型安全的核心保障。组件库设计中,合理的泛型约束能让组件API既灵活又类型安全,避免any泛滥。本文从实际场景出发,演示7种高频使用的类型编程模式,每种都附带可运行的代码示例。
泛型约束:限制类型参数的范围
未约束的泛型只能访问Object类型的方法。通过extends关键字约束泛型,可以要求类型参数具备特定属性。
// 基础泛型:T可以是任何类型
function getProperty<T>(obj: T, key: string) {
return obj[key]; // 报错:T上不存在索引签名
}
// 泛型约束:要求T有length属性
interface HasLength {
length: number;
}
function logLength<T extends HasLength>(value: T): T {
console.log(value.length);
return value;
}
logLength("hello"); // string有length属性,通过
logLength([1, 2, 3]); // array有length属性,通过
logLength(123); // number没有length属性,编译报错
keyof操作符:提取类型的键集合
function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] {
return obj[key];
}
const user = {
name: "张三",
age: 28,
email: "zhangsan@example.com"
};
// K被推断为"name" | "age" | "email"
// 返回类型自动推断为string | number
const name = getProperty(user, "name"); // 类型: string
const age = getProperty(user, "age"); // 类型: number
const invalid = getProperty(user, "phone"); // 编译报错: "phone"不在keyof user中
Vue3生态中,这个模式常用于组件Props的类型推导,确保传入的属性名合法且类型正确。
条件类型:类型层面的if-else
// 基本语法: T extends U ? X : Y
type IsString<T> = T extends string ? true : false;
type A = IsString<string>; // true
type B = IsString<number>; // false
type C = IsString<"hello">; // true (字面量类型也是string的子类型)
// 条件类型的分布特性
type ExcludeString<T> = T extends string ? never : T;
type Mixed = string | number | boolean;
type Filtered = ExcludeString<Mixed>; // number | boolean
// 分布过程: string→never, number→number, boolean→boolean
// never被忽略,结果为 number | boolean
infer关键字:在条件类型中提取类型
// 提取函数返回值类型
type ReturnType<T> = T extends (...args: any[]) => infer R ? R : never;
function getUser() {
return { name: "李四", age: 30 };
}
type UserType = ReturnType<typeof getUser>;
// 等价于 { name: string; age: number }
// 提取Promise的泛型参数
type UnwrapPromise<T> = T extends Promise<infer U> ? U : T;
type Result = UnwrapPromise<Promise<string>>; // string
type Result2 = UnwrapPromise<Promise<number[]>>; // number[]
type Result3 = UnwrapPromise<string>; // string(非Promise原样返回)
// 提取数组元素类型
type ElementOf<T> = T extends (infer E)[] ? E : never;
type Item = ElementOf<string[]>; // string
type Item2 = ElementOf<number[]>; // number
映射类型:批量转换类型属性
// 把所有属性变为只读
type Readonly<T> = {
readonly [P in keyof T]: T[P];
};
// 把所有属性变为可选
type Partial<T> = {
[P in keyof T]?: T[P];
};
// 把所有属性变为可空
type Nullable<T> = {
[P in keyof T]: T[P] | null;
};
interface User {
id: number;
name: string;
email: string;
}
type OptionalUser = Partial<User>;
// { id?: number; name?: string; email?: string; }
type ReadonlyUser = Readonly<User>;
// { readonly id: number; readonly name: string; readonly email: string; }
模板字面量类型:字符串级别的类型操作
// 事件监听器类型生成
type EventName = "click" | "focus" | "blur";
type EventHandler = `on${Capitalize<EventName>}`;
// "onClick" | "onFocus" | "onBlur"
// API路径类型
type ApiVersion = "v1" | "v2";
type ApiEndpoint = `/api/${ApiVersion}/${string}`;
// 匹配: "/api/v1/users" "/api/v2/orders" 等
// 属性名 getter 方法生成
type Getters<T> = {
[K in keyof T as `get${Capitalize<string & K>}`]: () => T[K];
};
interface Person {
name: string;
age: number;
}
type PersonGetters = Getters<Person>;
// { getName: () => string; getAge: () => number; }
跨端小程序开发中,这个模式可以自动生成各端API的调用方法,减少手写声明的工作量。
实战:类型安全的事件总线
// 定义事件映射
interface EventMap {
login: { userId: string; token: string };
logout: void;
message: { from: string; content: string };
error: { code: number; message: string };
}
class TypedEventEmitter<T extends Record<string, any>> {
private listeners: { [K in keyof T]?: Array<(payload: T[K]) => void> } = {};
on<K extends keyof T>(event: K, handler: (payload: T[K]) => void): void {
if (!this.listeners[event]) {
this.listeners[event] = [];
}
this.listeners[event]!.push(handler);
}
emit<K extends keyof T>(event: K, payload: T[K]): void {
this.listeners[event]?.forEach(handler => handler(payload));
}
off<K extends keyof T>(event: K, handler: (payload: T[K]) => void): void {
const arr = this.listeners[event];
if (arr) {
const idx = arr.indexOf(handler);
if (idx >= 0) arr.splice(idx, 1);
}
}
}
// 使用
const bus = new TypedEventEmitter<EventMap>();
// 类型安全:payload类型自动推导
bus.on("login", (data) => {
console.log(data.userId); // string
console.log(data.token); // string
});
bus.emit("login", { userId: "u123", token: "tk456" }); // 类型正确
bus.emit("login", { userId: "u123" }); // 编译报错: 缺少token
bus.emit("message", { from: "a", content: "hi" }); // 类型正确
bus.on("error", (data) => {
console.log(data.code); // number
console.log(data.message); // string
});
响应式布局项目中,事件总线的类型安全尤为重要——编译期拦截不匹配的事件类型,避免运行时的undefined错误。Web性能优化中,类型完整的代码也便于Tree-shaking,减少打包体积。
TypeScript类型体操的目标不是炫技,而是用编译期检查替代运行时调试。合理的类型约束让重构更安全、API更清晰、协作更顺畅。类型系统投入的学习成本会在项目维护阶段数倍返还。
原创文章,作者:小编,如若转载,请注明出处:https://www.yunthe.com/typescript-lei-xing-ti-cao-shi-zhan-fan-xing-yue-shu-yu/