泛型
泛型(generic),即泛指的类型,是指在定义函数、类、接口时不预先指定具体类型,而是在使用时再指定类型。以尖括号 <>
定义,括号里面是泛型名称,一般使用<T>
表示泛型。
泛型函数
先看一个联合类型的例子,函数参数可以是字符串或者数字:
function foo(a: string | number) {}
foo('hello');
使用泛型后的例子如下,定义了一个<T>
泛型,在调用函数时要声明泛型的具体类型,TS 也会类型推断。
function foo<T>(a: T): T {
return a;
}
foo<string>('hello');
foo<number>(1);
foo(2); // 类型推断
定义type
或者interface
,可以传入泛型参数,达到类型复用的效果:
type PropsType<T> = {
[key: string]: T;
};
const obj: PropsType<number> = { a: 1, b: 2 };
泛型数组
有两种表示方式:Array<T>
和 T[]
function foo<T>(a: T[]) {}
foo<string>(['hello', 'world']);
function bar<T>(a: Array<T>) {}
bar<string>(['hello', 'world']);
泛型接口
基本的泛型接口
interface Container<T> {
value: T;
}
const foo: Container<string> = { value: 'Hello, World!' };
const bar: Container<number> = { value: 123 };
泛型接口和函数
定义一个接口来描述泛型函数的结构。重点在描述函数的结构,函数的具体实现要按照这个结构来。
示例 1: 该函数可以接受任意类型的参数并返回相同类型的值
interface FooFun<T> {
(value: T): T;
}
const foo: FooFun<string> = (value: string) => value;
foo('Hello');
示例 2:
interface CreateArrayFun<T> {
(length: number, value: T): Array<T>;
}
const createArray: CreateArrayFun<any> = <T>(length: number, value: T): T[] => {
return new Array(length).fill(value) as T[];
};
console.log(createArray(3, 'x')); // ['x', 'x', 'x']
console.log(createArray(3, 123)); // [123, 123, 123]
泛型接口和类
interface GenericClass<T> {
value: T;
setValue(newValue: T): void;
}
class MyClass<T> implements GenericClass<T> {
value: T;
constructor(initialValue: T) {
this.value = initialValue;
}
setValue(newValue: T): void {
this.value = newValue;
}
}
const foo = new MyClass<string>('hello');
foo.setValue('world');
const bar = new MyClass<number>(18);
bar.setValue(23);
定义多个泛型参数
interface Item<K, V> {
key: K;
value: V;
}
const foo: Item<string, number> = { key: 'age', value: 30 };
泛型类
class Foo<T> {
constructor(private value: T) {}
getValue(): T {
return this.value;
}
}
const foo = new Foo<string>('hello');
泛型继承
先看一个例子:
class Foo<T> {
constructor(private first: T[]) {}
say(index: number): T {
return this.first[index];
}
}
const instance = new Foo<string>(['hello', 'world']);
instance.say(1); // 'world'
在这个例子中,假如传入对象数组,希望调用 say 方法返回传入的 name 值,直接改成this.first[index].name
会报错
Property 'name' does not exist on type 'T'
,因为 T 类型参数是任意类型,无法 保证能访问到 name 属性。
interface Person {
name: string;
}
class Foo<T extends Person> {
constructor(private first: T[]) {}
say(index: number): string {
return this.first[index].name;
}
}
const params = [{ name: 'z' }, { name: 'g' }, { name: 'h' }];
const instance = new Foo(params);
instance.say(1); // 'g'
这时用到了泛型的继承,T 继承了接口 Person,这意味着传给 T 的任何类型都必须要包含 name: string
属性,所以在 say 方法里能安全的访问 name 属性。
前面省略了参数类型,因为泛型的类型推断,所以也不会报错,完整的写法如下:
const instance = new Foo<{ name: string }>(params);
还要注意say(index: number): string {}
不能写成say(index: number): T {}
。原意是返回 Person 接口中定义的 name 属性的值,该值明确为 string 类型。而 T 是一个泛型参数,它代表的是传入的整个对象类型,不仅仅是 name 属性的类型。
泛型约束
类型约束的语法是使用 extends
关键字。
示例 1:
class Foo<T> {
constructor(private first: T[]) {}
say(index: number): T {
return this.first[index];
}
}
const gen1 = new Foo<boolean>([true, false]);
const gen2 = new Foo<string>(['hello', 'world']);
const gen3 = new Foo<number>([1, 2]);
示例中的泛型可以是string
、number
、boolean
等类型。如果要进行泛型约束,使其类型只能是number
或者string
,如下:
class Foo<T extends number | string> {
// 同上
}
type StringOrNumber = string | number;
class Foo<T extends StringOrNumber> {}
示例 2:约束函数需要一个对象作为参数,并且该对象必须有一个 name 属性
interface HasName {
name: string;
}
function getName<T extends HasName>(obj: T): string {
return obj.name;
}
const person = { name: 'Alice', age: 1 };
const result = getName(person);
默认类型参数
function foo<T = string>(a: T) {
console.log(a);
}
foo('hi');
定义多个泛型
比如定义两个泛型 T、P
function foo<T, P>(a: T, b: P) {}
foo<string, number>('hi', 666);
常见的类型参数名称如下,只是惯例,不是硬性规定。
- T:Type,表示单个类型
- U:表示第二个类型
- K:Key,键名
- V:Value,键值
- P:Params,参数类型
- E:Element,集合的元素类型
- R:Result,函数的返回结果
- N:Number,数字类型
泛型工具
Record
type Record<K extends string | number | symbol, T> = { [P in K]: T };
构建一个类型,包含指定的属性且必填
type Props = Record<'x' | 'y', number>;
// 等同于
// type Props = {
// x: number;
// y: number;
// };
type Props = Record<string, unknown>;
// 等同于
// type Props = {
// [x: string]: unknown;
// }
Omit<T, K exdends keyof any>
排除接口中指定的属性(除了某些项,其余的全部都要),第一个参数表示要继承的类型,第二个参数表示要省略的属性,多个属性用竖线隔开
interface UserProps {
name: string;
age: number;
sex: string;
}
type UserProps1 = Omit<UserProps, 'age'>;
// 等同于
// type UserProps1 = {
// name: string;
// sex: string;
// }
type UserProps2 = Omit<UserProps, 'age' | 'sex'>;
// 等同于
// type UserProps2 = {
// name: string;
// }
Pick
选取类型中的指定类型(除了某些项,其余的都不要)
type UserProps3 = Pick<UserProps, 'age' | 'sex'>;
// type UserProps3 = {
// age: number;
// sex: string;
// }
Partial
将类型的所有属性变为可选
type UserProps4 = Partial<UserProps>;
// 等同于
// type UserProps4 = {
// name?: string | undefined;
// age?: number | undefined;
// sex?: string | undefined;
// };
Readonly
将类型的所有属性变为只读
type UserProps5 = Readonly<UserProps>;
// 等同于
// type UserProps5 = {
// readonly name: string;
// readonly age: number;
// readonly sex: string;
// };
Required
将类型的所有属性变为必填
type UserProps6 = Required<{ x: number; y?: number }>;
// 等同于
// type UserProps6 = {
// x: number;
// y: number;
// };
Exclude(T, U)
排除联合类型中指定的子类型
type UserProps7 = Exclude<string | number | boolean, number>;
// 等同于
// type UserProps7 = string | boolean
Extract(T, U)
提取联合类型中的指定类型,如果存在则返回该类型,不存在则返回 never
type UserProps8 = Extract<string | number | boolean, number>;
type UserProps9 = Extract<string | number | boolean, []>;
// 等同于
// type UserProps8 = number;
// type UserProps9 = never;
NonNullable
过 滤联合类型中的 null 和 undefined 类型
type UserProps9 = NonNullable<string | null | undefined>;
// 等同于
// type UserProps9 = string;
Parameters<T extends (...args: any) => any>
获取函数的所有参数类型
type Fun = (a: string, b: number) => void;
type Props = Parameters<Fun>;
// 等同于
// type Props = [a: string, b: number]
const F1 = (a: string, b: number) => a + b;
type Props = Parameters<typeof F1>;
ReturnType
获取函数的返回值类型
type Fun = (a: number, b: number) => number;
const F1 = (a: number, b: number) => a + b;
type Props1 = ReturnType<Fun>;
type Props2 = ReturnType<typeof F1>;
// 等同于
// type Props = number;