TypeScript学习笔记(五) - 泛型

时间:2023-01-28 14:51:11

本篇将介绍在TypeScript如何使用泛型。

一、泛型方法

在TypeScript里,声明泛型方法有以下两种方式:

1 function generics_func1<T>(arg: T): T {
2     return arg;
3 }
4 // 或者
5 let generics_func2: <T>(arg: T) => T = function (arg) {
6     return arg;
7 }

调用方式也有两种:

1 generics_func1<string>('Hello world');
2 // 或者
3 generics_func2('Hello world');

第二种调用方式可以省略类型参数,因为编译器会根据传入参数来自动识别对应的类型。

在之前介绍的基本类型里,有一个any类型,表示不确定的类型。在具体使用时,可以代替任意类型,比如如下方法定义和实现:

1 function any_func(arg: any): any {
2     return arg;
3 }
4 
5 any_func(1);
6 any_func('Hello world!');
7 any_func(['1', '2']);

看似与泛型方法类似,但是还是有区别的。比如如下例子:

 1 // 方法一:带有any参数的方法
 2 function any_func(arg: any): any {
 3     console.log(arg.length);
 4     return arg;
 5 }
 6 
 7 // 方法二:Array泛型方法
 8 function array_func<T>(arg: Array<T>): Array<T> {
 9     console.log(arg.length);
10     return arg;
11 }

在方法一的方法体里,打印了arg参数的length属性。因为any可以代替任意类型,所以该方法在传入参数不是数组或者带有length属性对象时,会抛出异常。而方法二定义了参数类型是Array的泛型类型,肯定会有length属性,所以不会抛出异常。

从上面这个例子可以看出,泛型类型相比较any类型来说,在某些情况下会带有类型本身的一些信息,而any类型则没有。

 

二、泛型类

以下是一个泛型类的定义和调用

 1 class Generics_Demo<T>{
 2     value: T;
 3     show(): T {
 4         return this.value;
 5     }
 6 }
 7 
 8 let gene_demo1 = new Generics_Demo<number>();
 9 gene_demo1.value = 1;
10 console.log(gene_demo1.show());                                     // 调用方法
11 
12 gene_demo1.show = function () { return gene_demo1.value + 1; }      // 赋值新方法,返回值类型必须是number
13 console.log(gene_demo1.show());

通过指定明确类型的泛型类的实例,对属性赋值时,必须满足实际类型的约束。

 

三、泛型类型

以下几个例子都是利用泛型类型定义变量或者方法参数的类型的示例

1. 泛型接口

1 interface Generics_interface {
2     <T>(arg: T): T;
3 }
4 
5 function func_demo<T>(arg: T): T {
6     return arg;
7 }
8 
9 let func1: Generics_interface = func_demo;

上面的例子里,接口只有一个泛型方法成员。则用此接口类型定义的变量就是一个与成员类型一致的泛型方法。

将上面例子的泛型接口稍微改一下

 1 interface Generics_interface<T> {
 2     (arg: T): T;
 3 }
 4 
 5 function func_demo<T>(arg: T): T {
 6     return arg;
 7 }
 8 
 9 let func1: Generics_interface<number> = func_demo;
10 func1(123);     // 正确类型的实际参数
11 func1('123');   // 错误类型的实际参数

通过在接口上声明泛型,声明变量时明确指定泛型的具体类型,则赋值的方法将自动带上具体的类型约束。

2. 泛型类型继承

 1 interface LengthInterface {
 2     length: number;
 3 }
 4 
 5 function func_demo<T extends LengthInterface>(arg: T): T {
 6     console.log(arg.length);
 7     return arg;
 8 }
 9 
10 func_demo({ a: 1, length: 2 });     // 含有length属性的对象
11 func_demo([1, 2]);                  // 数组类型

上面的例子里,泛型类型继承自一个拥有length属性成员的接口,泛型类型将自动加上length属性的约束。调用时只有符合条件的对象才能正确赋值。

 1 function copy<T extends U, U>(source: U, target: T): T {
 2     for (let prop in source) {
 3         target[prop] = source[prop];
 4     }
 5 
 6     return target;
 7 }
 8 
 9 copy({ a: 1, b: 2 }, { a: 2, b: 3, c: 4 });         // 正确的实际参数
10 copy({ a: 1, b: 2 }, { q: 2, c: 4 });               // 错误的实际参数

在上面的例子里,一个泛型类型继承自另外一个泛型类型。在方法调用时,就必须确保继承类型对应的参数对象属性完全包含被继承类型对应的参数对象。