TypeScript泛型的基本用法
TypeScript泛型(Generics)是实现类型参数化的核心机制,允许在定义函数、接口和类时使用类型占位符,在使用时传入具体类型。泛型的价值在于在不牺牲类型安全的前提下实现组件复用,编译期完成类型推导,运行时零开销。
一个简单的泛型函数示例:
function firstElement<T>(arr: T[]): T | undefined {
return arr[0];
}
const num = firstElement([1, 2, 3]); // 类型推导为 number
const str = firstElement(['a', 'b', 'c']); // 类型推导为 string
const empty = firstElement([]); // 类型推导为 undefined
T是类型参数,调用时编译器根据实参自动推导T的具体类型。多个类型参数可以同时使用:
function pair<K, V>(key: K, value: V): { key: K; value: V } {
return { key, value };
}
const p1 = pair('id', 42); // { key: string; value: number }
const p2 = pair(0, 'admin'); // { key: number; value: string }
泛型约束(Constraints)通过extends关键字限制类型参数的范围,确保传入类型具备必要属性:
// 约束T必须包含length属性
function getLength<T extends { length: number }>(item: T): number {
return item.length;
}
getLength('hello'); // 5
getLength([1, 2, 3]); // 3
// getLength(123); // 编译错误:number没有length属性
条件类型的语法与分发特性
条件类型(Conditional Types)是TypeScript中最强大的类型构造工具,语法形式为T extends U ? X : Y,类似于三元运算符但作用于类型层面。
// 基础条件类型
type IsString<T> = T extends string ? true : false;
type A = IsString<'hello'>; // true
type B = IsString<42>; // false
条件类型的分发(Distribute)特性在联合类型上表现特殊。当类型参数是联合类型时,条件类型会逐成员分发后合并结果:
type ToArray<T> = T extends any ? T[] : never;
// 联合类型分发:分别对string和number应用
type R = ToArray<string | number>;
// 等价于 ToArray<string> | ToArray<number>
// 结果为 string[] | number[]
如果需要阻止分发行为,用方括号包裹类型参数:
type ToArrayNonDist<T> = [T] extends [any] ? T[] : never;
type R2 = ToArrayNonDist<string | number>;
// 结果为 (string | number)[]
排除类型是条件类型的常见应用场景:
// 排除null和undefined
type NonNullable<T> = T extends null | undefined ? never : T;
type Clean = NonNullable<string | null | undefined>; // string
// 从联合类型中排除指定类型
type Without<T, U> = T extends U ? never : T;
type Result = Without<'a' | 'b' | 'c', 'b'>; // 'a' | 'c'
infer关键字与类型提取
infer关键字在条件类型的extends子句中声明类型变量,从已有类型中提取组成部分。这是TypeScript类型体操的核心技巧。
// 提取函数返回类型
type ReturnType<T> = T extends (...args: any[]) => infer R ? R : never;
type F1 = ReturnType<() => string>; // string
type F2 = ReturnType<(x: number) => boolean>; // boolean
// 提取Promise的泛型参数
type Unpacked<T> = T extends Promise<infer U> ? U : T;
type P1 = Unpacked<Promise<number>>; // number
type P2 = Unpacked<string>; // string
infer可以提取多个类型变量,在复杂类型中组合使用:
// 提取构造函数实例类型
type InstanceType<T> = T extends new (...args: any[]) => infer R ? R : never;
class Animal { constructor(public name: string) {} }
type A = InstanceType<typeof Animal>; // Animal
// 提取数组元素类型
type ElementOf<T> = T extends (infer E)[] ? E : never;
type E1 = ElementOf<string[]>; // string
type E2 = ElementOf<number[]>; // number
// 提取对象value类型
type ValueOf<T> = T extends Record<any, infer V> ? V : never;
const config = { host: 'localhost', port: 8080 } as const;
type V = ValueOf<typeof config>; // 'localhost' | 8080
映射类型与内置工具类型
映射类型(Mapped Types)基于已有类型通过循环语法生成新类型,是类型转换的基础。语法为{ [K in keyof T]: NewType }:
// 将所有属性变为只读
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 }
TypeScript内置了一组工具类型,实际开发中优先使用内置类型而非手写映射:
// Pick:选取部分属性
type UserBasic = Pick<User, 'id' | 'name'>;
// { id: number; name: string }
// Omit:排除部分属性
type UserWithoutId = Omit<User, 'id'>;
// { name: string; email: string }
// Record:构造键值对类型
type UserMap = Record<string, User>;
// Required:移除所有可选标记
type CompleteUser = Required<OptionalUser>;
// Parameters:提取函数参数类型为元组
type Params = Parameters<(x: number, y: string) => void>;
// [number, string]
模板字面量类型与字符串操作
模板字面量类型(Template Literal Types)是TypeScript 4.1引入的特性,可以在类型层面对字符串进行拼接和变换:
// 基础模板字面量类型
type Greeting = `hello ${string}`;
const g: Greeting = 'hello world'; // 合法
// 结合映射类型生成getter/setter类型
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 }
内置的字符串工具类型包括Uppercase、Lowercase、Capitalize和Uncapitalize,配合模板字面量类型可以实现类型安全的命名转换:
// 事件监听器类型生成
type EventName<T extends string> = `on${Capitalize<T>}`;
type Events = 'click' | 'change' | 'submit';
type Handlers = {
[K in Events as EventName<K>]: (e: Event) => void;
};
// { onClick: (e: Event) => void; onChange: ...; onSubmit: ... }
这种模式在前端组件库开发中极为实用,可以根据props接口自动推导事件处理器类型,避免手动维护重复的类型定义。
泛型在React组件中的实践应用
React组件中使用泛型可以实现类型安全的复用组件。以一个通用的列表组件为例:
interface ListProps<T> {
items: T[];
renderItem: (item: T, index: number) => React.ReactNode;
keyExtractor: (item: T) => string;
}
function List<T>({ items, renderItem, keyExtractor }: ListProps<T>) {
return (
<ul>
{items.map((item, index) => (
<li key={keyExtractor(item)}>
{renderItem(item, index)}
</li>
))}
</ul>
);
}
// 使用时T自动推导为User
const users: User[] = [
{ id: 1, name: 'Alice', email: 'a@test.com' },
{ id: 2, name: 'Bob', email: 'b@test.com' },
];
<List
items={users}
renderItem={(user) => <span>{user.name}</span>}
keyExtractor={(user) => String(user.id)}
/>
renderItem回调中的item类型自动推导为User,编写JSX时可以获得完整的属性提示。这种模式在表格、下拉选择、树形控件等通用组件中广泛应用,是前端工程化中类型安全的核心保障。
原创文章,作者:小编,如若转载,请注明出处:https://www.yunthe.com/typescript-fan-xing-yu-gao-ji-lei-xing-shi-zhan-tiao-jian/