泛型是指定一个表示类型的变量,用它来代替某个实际的类型用于编程,而后通过实际调用时传入或推导的类型来对其进行替换,以达到一段使用泛型程序可以实际适应不同类型的目的。为了实现泛型所要达到的目的,我们也可采用指定类型为 any 或者为每一种可能的类型都写一个方法(重载),但这严重违反抽象和复用代码的原则。所以在考虑可重用组件的时候,我们应该使用泛型。
泛型其实是 C# 和 Java 这种强类型语言中的一个特性,TypeScript 把这一特性引进到了弱类型语言 JavaScript 中,对于没接触过强类型语言的前端 coder,理解这个概念可能有点吃力,如果看不太懂,可以先去了解一下 Java 中的泛型,下面进入正题。
初识泛型
定义泛型:我们把要传入函数的参数类型设为一个类型变量 T ,它能够帮助我们捕获用户传入的类型,之后出现 T 的地方,都会被替换成用户传入的类型。
function identity<T>(arg: T): T {
return arg;
}
function getFirst<T>(arr: T[]): T {
return arr[0];
}
console.log(identity(10)); // 10
console.log(identity('TS')); // TS
console.log(getFirst([1, 2, 3, 4])); // 1
console.log(getFirst(['a', 'b', 'c'])); // a
使用泛型:有两种使用泛型的方式,第一种是传入所有的参数,包括类型参数;第二种是不传类型参数,因为 TypeScript 的编译器会利用类型推论来确定参数的类型,推荐使用第二种。
function identity<T>(arg: T): T {
return arg;
}
function getFirst<T>(arr: T[]): T {
return arr[0];
}
// 使用一:传入所有参数
console.log(identity<number>(10)); // 10
console.log(identity<string>('TS')); // TS
console.log(getFirst<number>([1, 2, 3, 4])); // 1
console.log(getFirst<string>(['a', 'b', 'c'])); // a
// 使用二:不传参数类型
console.log(identity(10)); // 10
console.log(identity('TS')); // TS
console.log(getFirst([1, 2, 3, 4])); // 1
console.log(getFirst(['a', 'b', 'c'])); // a
泛型类型
一个泛型函数的类型如下:
<泛型变量名称>(参数1: 泛型变量, 参数2: 泛型变量, ...参数n: 泛型变量) => 泛型变量
可以以对象字面量的形式来定义泛型函数(这更像是接口),如:
let foo: { <T>(arg: T): void };
foo = function <T>(arg: T): void {
console.log(arg);
}
foo(13); // 13
将上面的例子中的 { <T>(arg: T): void }
改为接口,则有:
interface IGeneric {
<T>(arg: T): void
}
let foo: IGeneric = function <T>(arg: T): void {
console.log(arg)
}
foo(13); // 13
最后,接口中也可以使用泛型,这样子就锁定了代码里可以使用的类型,如:
interface IGeneric<T> {
(arg: T): void
}
function fn<T>(arg: T): void {
console.log(arg);
}
let myFn: IGeneric<number> = fn;
myFn(13); //13
泛型类
除了泛型接口,我们还可以创建泛型类(但是无法创建泛型枚举、泛型命名空间),泛型类使用 <> 包围泛型类型变量,如:
class Person<T> {
love: T;
say: (arg: T) => T;
}
let me = new Person<string>();
me.love = 'TS';
// me.love = 520; // ERROR
me.say = function(love: string){
return `my love is ${love}`;
}
console.log(me.say('TS')); // my love is TS
注意:类有两部分,分为静态部分和实例部分,泛型类的类型指的是实例部分的类型,静态属性不能使用该泛型类型。
泛型约束
泛型可以通过 extends 一个接口来实现泛型约束,写法如:<泛型变量 extends 接口>
,例子:
interface IArray {
length: number
}
function logIndex<T extends IArray>(arg: T): void {
for (let i = 0; i < arg.length; ++i) {
console.log(i)
}
}
let arr = [1, 2, 3]
// logIndex<number>(arr) // 报错
logIndex<number[]>(arr) // 允许
logIndex(arr) // 自动类型推导,允许
可以在泛型里使用类类型,如使用泛型创建工厂函数,需要引用构造函数的类类型,有:
function create<T>(c: { new(): T }): T {
return new c()
}