泛型

1. 泛型函数

定义一个 identity 函数

function identity<T>(arg: T): T {
    return arg;
}

给 identity 函数添加了 类型变量T. T 会捕获用户传入的类型,(比如: number),之后 T 就代表这个类型了。

这个版本的 identity函数 叫做泛型函数,因为 T 可以是任意的类型。

泛型函数的两种调用方法:

1. 传入所有的参数,包括类型参数,其中类型参数用 <> 括起来
const output = identity<string>('123');
即明确指定 T 就是 string, 因此会限制 arg 必须为 string 类型。
因此下面的代码会报错:  
const output = identity<string>(123);  因此 123 不是 string 类型,所以会报错。


2. 可以不传类型参数,编译器自己查看 '123' 的类型,自动推算出 T 为 string
const output = identity('123');

2. 泛型变量

想象下以下代码,因为 return 的值必然是个字符串,而 T 不一定是 string, 所以会报错。

function identity<T>(arg: T): T {
    return arg+'123';
}

又或者:

function identity<T>(arg: T): T {
    console.log(arg.length);  // T 类型不一定有 length 属性(T 为 string 时有),所以会报错
    return arg;
}

又因为数组具有 .length 属性,因此可以像下面这样:

function logIdentity<T>(arg: T[]): T[] {
    console.log(arg.length);    // 数组中确定是有length属性的
    return arg;
}

即我们可以把泛型变量T作为类型的一部分,实际类型 T[]T 构成,增加了灵活性。

3. 泛型类型

function identity<T>(arg: T): T {
    return arg;
}

const myIdentity: <T>(arg: T) => T = identity
这段代码其实就是 const myIdentity = identity; 
<T>(arg: T) => T 只是类型描述

const myIdentity: <T>(arg: T) => T = identity 又会等同于 const myIdentity: { <T>(arg: T): T } = identity

因此我们可以定义一个泛型接口:

interface GenericIdentityFn {
    <T>(arg: T): T;
}

function identity<T>(arg: T): T{
    return arg;
}

const myIdentity: GenericIdentityFn = identity

接受参数的泛型接口:

// 接受一个类型参数,作为T
// 这样接口里的其他成员也能知道这个参数的类型
interface GenericIdentityFn<T> {
    (arg: T): T;
}

function identity<T>(arg: T) :T {
    return arg;
}

const myIdentity: GenericIdentityFn<number> = identity;

4. 泛型类

与泛型接口类似

class GenericNumber<T> {
    zeroValue: T;
    add: (x:T, y:T) => T;
}

const myGenericNumber = new GenericNumber<number>();  // 必须传入类型 number
myGenericNumber.zeroValue = 1;
myGenericNumber.add = function(x,y) = { return x + y };

类有静态部分和实例部分,泛型类只能用于实例部分的类型,类的静态属性不能使用这个泛型类型。

5. 泛型约束

首先以下代码会报错:

function logIdentity<T>(arg: T):T {
    console.log(arg.length); // 不一定会有 .length 属性
    return arg;
}

因此我们需要约束类型 T 必须要有 length 属性, 我们可以定义一个接口来描述约束条件,使用接口和extends关键字来实现约束:

interface LengthWise {
    length: number;
}

function identity<T extends LengthWise>(arg: T) :T {
    console.log(arg.length);  // 不会报错,接口限制了类型 T 会有 length 属性
    return arg;
}

此时,要调用 identity, 那么参数必须有 length 属性

5.1 泛型约束中使用类型参数

声明一个类型参数,被另一个类型参数所约束。

function getProperty<T, K extends keyof T>(obj: T, key:K) {
    return obj[key];
}

const x = {a: 1, b :2};

getProperty(x,'a')
getProperty(x,'m')    // m 不是对象 x 的key,因此会报错

关于 keyof:

interface Person {
    name: string;
    age: number;
    location: string;
}

type k1 = keyof Person;    // "name" | "age" | "location"   对象的属性名
type k1 = keyof Person[];    // "length" | "push" | "pop"  数组的属性名

因此 K extends keyof T,则 K 必须是对象 T 的某个属性名

5.2 泛型中使用类类型

Last updated