# 泛型
在值和类型小节我们强调 类型是一个抽象概念,表示一系列值的集合,确定了值的操作属性,那如何控制具有特定特征的类型? 本章学习泛型用于解决此类问题。
# 泛型概述
采用 <泛型符号> 申明一个泛型,其中泛型符号表示任意类型。
// 在函数申明时采用泛型符号, T 指代任意类型,参数和返回值均可采用此类型
function identity<T>(arg: T): T {
return arg;
}
// 泛型符号名称为指示符,可以取任何合法名称
let myIdentity1: <ABC>(arg: ABC) => ABC = identity;
// 注意 `<>` 是申明在 `()` 之前!
let myIdentity2: { <T>(arg: T): T } = identity;
# 泛型函数(generic)
function createArr(length,data) {
return [val1,val2,...]
}
一个创建数组的函数 createArr,返回的数组元素 val1,val2 为相同类型,但该类型可能为字符串,数值,或对象。如何进行限制,若采用 any[] 并不能表示数组元素类型相同这个特征。申明泛型解决此问题。
// 采用 <T> 表示创建一个泛型 T, 注意 T 指代一个类型也可换为任何字符
type CreateArr = <T>(length: number, data: T) => T[];
let createArr: CreateArr = (length: number, data: any) => {
let arr = [];
for (let i = 0; i < length; i++) {
arr.push(data);
}
return arr;
};
// 创建长度为 10 ,元素为 1 的数组
console.log(createArr(10, 1));
// 创建长度为 10 ,元素为 'foo' 的数组
console.log(createArr(10, 'foo'));
// 创建长度为 10 ,元素为 { a: 1 } 的数组
console.log(createArr(10, { a: 1 }));
另一个示例用来限制交换元素类型必须相同
// 该函数约束只能交换相同类型的元祖数据
function swapSameType<S>(tuple: [S, S]): S[] {
return [tuple[1], tuple[0]];
}
// 交换数值
console.log(swapSameType([1, 2]));
// 交换字符串
console.log(swapSameType(['1', '2']));
// 交换对象
console.log(swapSameType([{ a: 1 }, { b: 1 }]));
// 报错,若数组元素类型不一样会报错
console.log(swapSameType([1, '1']));
# 多个泛型参数
可以同时申明多个泛型符号 <泛型参数1,泛型参数2,...>。
// 定义泛型参数 T,U 表示交换函数可交换任意类型
// 泛型名称 T, U 可以为任意合法名称,只是一个指示符号
function swap<T, U>(tuple: [T, U]): [U, T] {
return [tuple[1], tuple[0]];
}
// 合法
console.log(swap([10, 'foo']));
// 合法
console.log(swap([10, 10]));
// 合法
console.log(swap([null, undefined]));
# 泛型参数默认值
有时期望泛型函数的参数支持可选,可采用给泛型参数设置默认值解决此类问题
export function getObjectKeys<Obj extends Record<string, any>,ChoiceKeys extends (keyof Obj)[] = (keyof Obj)[]>(obj: Obj,keys: ChoiceKeys) {
return Object.keys(obj).filter(key => keys.includes(key))
}
export const a = getObjectKeys<{a: number, b: number}>({a: 1, b:2}, ['a'])# 接口泛型
也可采用接口定义函数泛型。
interface InterfaceName {
<T>(arg: T): T;
}
你可以将泛型申明 <T> 提升到接口名后。
interface InterfaceName<T> {
(arg: T): T;
}
采用外部申明泛型,你可以结合类型断言,指定接口接受特定类型。
// 定义输入参数和返回值为类型 T 的泛型函数
interface InterfaceName {
<T>(arg: T): T;
}
// 效果同上,注意泛型申明提升到了接口名后
interface InterfaceName1<T> {
(arg: T): T;
}
// 无法采用类型断言,限制为特定类型
let val1: InterfaceName;
// 表示参数类型必须为函数且函数输入和返回值必须为 number 类型
let val2: InterfaceName1<number>;
# 类中使用泛型
也可在类中使用泛型。
# 泛型约束
采用泛型约束语法实现, 限定泛型具有某种特征,通过 extends 让泛型参数继承接口特性
interface Lengthwise {
length: number;
}
// 泛型 T 利用 extends 扩展接口,实现类型约束
function loggingIdentity<T extends Lengthwise>(arg: T) {
console.log(arg.length);
return arg;
}
loggingIdentity([1, 2, 3]);
另一示例,限制工厂方法只能传入特定的构造函数。
class Animal {
// !: 表示明确类型断言,避免 TypeScript 检查到未对 numLegs 赋值报错
numLegs!: number;
}
class Lion extends Animal {
name!: string;
}
class Bug {}
// 采用泛型 A 限制构造器的类型必须是 Animal 及其子类
function createAnimal<A extends Animal>(C: new () => A): A {
return new C();
}
let a1 = createAnimal(Animal);
let l1 = createAnimal(Lion);
// 由于 Bug 不继承自 Animal,此处检查会报错
let p1 = createAnimal(Bug);
# 总结
本节我们学习了修饰类型的类型泛型。
- 利用
<T>的方式申明泛型符号 - 函数中泛型申明放在
()之前,可同时定义多个泛型符号 - 采用
interface 接口名 <T>定义泛型,可以结合类型断言限制泛型为特定类型 - 利用
T extends 类型的方式约束泛型