泛型 ( Generics ) 是指在定义函数、接口或类等变量时,先不指定具体类型,而是在使用时再指定类型的一种特性。
举个栗子🌰:
编写一个函数,接收一个值 value 和次数 length (长度),返回一个 value 值重复 length 次的数组,如 generateArray('x', 3); 返回 ['x', 'x', 'x'] ;
JS 实现:
const generateArray = (value, length) => {/*Array(length).join(',').split(',').map(() => value);Array(length).join(',').split(',').fill(value);Array.from({length}, () => value);Array(length).fill(value);*/return Array(length).fill(value);}const arr = generateArray('x', 5);console.log(arr);
TS 实现:
function generateArray(value: any, length: number): any[] {return Array(length).fill(value);}const arr = generateArray('x', 5);console.log(arr);
上述代码能够实现基本功能需求,但对于 TypeScript 而言,它的类型推论及提示完全不够( 返回数组的类型永远是 : any[] ),如果要期望函数能根据从传入的 value 类型,进而推论出返回的数组类型,就需要使用泛型:
TS 泛型实现:
function generateArray<T>(value: T, length: number): T[] {
return Array(length).fill(value);
}
const arr = generateArray('x', 5);
console.log(arr);
其中函数名 generateArray 后的 <T> 表示声明指代任意传入类型参数,后面的 value: T 表示具体传入类型, : T[] 表示返回传入类型的数组,其中 : T[] 可用泛型表达式 Array<T> 代替。
1 多个类型参数
定义泛型时可定义多个类型参数,如元组交换顺序:
function swapTuple<F, S>(tuple: [F, S]): [S, F] {
return [tuple[1], tuple[0]];
}
const tuple_1: [string, number] = ['1', 2];
const tuple_2 = swapTuple(tuple_1);
2 泛型约束
使用泛型的类型参数时,函数内部并不知道传入的类型,进而不知道它的方法和属性,产生编译错误:
function isIterable<T>(arg: T): T {
/**
* ERROR:
* 类型“T”上不存在属性“length”。
* 类型“T”上不存在属性“size”。
*/
if (arg.length || arg.size) {
return arg;
}
}
要解决这个问题可以使用接口 ( Interfaces ) 对泛型 ( Generics ) 的形状进行约束,使其传入的类型参数必须符合接口类型。
interface cusIterable {
length?: number,
size?: number,
}
// 泛型的输入类型 T 继承于接口 cusIterable
function isIterable<T extends cusIterable>(arg: T): T {
if (arg.length || arg.size) {
return arg;
}
}
同样的,泛型能预测属性是否存在:
function hasProp<O, K extends keyof O>(obj: O, p: K): boolean {
return typeof obj[p] !== "undefined"
}
let o = {
name: "Tom",
age: 18
}
hasProp(o, "name");
// ERROR: 类型“"gender"”的参数不能赋给类型“"name" | "age"”的参数。
hasProp(o, "gender");
3 泛型接口
前面知道,可以使用接口来约束函数的形状,当然也可以使用含有泛型的接口来约束函数的形状:
// 声明泛型接口
interface IGenerateArray {
<T>(value: T, length: number): Array<T>
}
let generateArray: IGenerateArray;
generateArray = function<T>(value: T, length: number): T[] {
return Array(length).fill(value);
}
4 泛型类
与泛型接口类似,泛型也能用于约束类的声明:
// 声明泛型类
class GenericNumber<T> {
zeroValue: T;
add: (x: T, y: T) => T;
}
let myGenericNumber = new GenericNumber<number>();
myGenericNumber.zeroValue = 0;
myGenericNumber.add = function(x: number, y: number): number {
return x + y;
};
5 泛型参数的默认类型
自 TypeScript 2.3 之后,可以用 <T = type> 的格式,为泛型的参数类型指定默认类型,在没有为泛型传入参数类型,也没有推论出参数类型时生效 (产生默认提示)。
function generateArray<T = string>(value: T, length: number): Array<T> {
return Array(length).fill(value);
}
6 交叉类型
交叉类型常用于合并对象,格式: t1 & t2 (与联合类型不同)
function crossType<F extends object, T extends object>(o1: F, o2: T): F & T {
const r = {} as F & T
for (const k in o1) {
r[k] = o1[k] as any
}
for (const k in o2) {
if (!r.hasOwnProperty(k)) {
r[k] = o2[k] as any
}
}
return r;
}
class Human {
name: string;
constructor(name: string) {
this.name = name;
}
}
interface Man {
gender: string,
introduce: (favorite: string) => string
}
class Male implements Man {
gender = "男";
introduce(favorite: string): string {
return `性别:${this.gender},爱好:${favorite}。`;
}
}
class Female implements Man {
gender = "女";
introduce(favorite: string): string {
return `性别:${this.gender},爱好:${favorite}。`;
}
}
const m = crossType(new Human("杰克"), new Male());
console.log(m.name, m.introduce("唱歌"));
const f = crossType(new Human("罗丝"), new Female());
console.log(f.name, f.introduce("跳舞"));
