关于TS的泛型

134 阅读2分钟

1. 泛型是什么

泛型,顾名思义,就是可以适用于多个类型,使用类型变量比如T帮助我们捕获传入的类型,之后我们就可以继续使用这个类型。

如下定义了一个identity泛型函数,添加了类型变量T,它就可以帮助捕获我们传入的变量value的类型,然后再次使用T作为返回值类型,这样就约束了参数类型和返回值类型是相同的了。

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

2. 泛型类型和泛型接口

const myIdentity: <T>(arg: T) => T = identity;

泛型类型和非泛型类型没有什么区别,只是传入了类型参数。

我们还可以使用对象字面量的形式来声明函数类型:

const myIdentity: { <T>(arg: T): T } = identity;

这样就引入了泛型接口:

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

const myIdentity: GenericIdentityFn = identity;

再进一步把类型变量T作为接口的参数,使用接口的时候再传入类型参数,就锁定了函数中使用的类型:

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

const myIdentity: GenericIdentityFn<number> = identity;

3. 泛型类

在类名后面跟上类型变量,就可以在类中使用这些泛型类型。

class GenericPerson<T, U> {  
  name: T;
  getAge: (age: U) => U;
}
let person = new GenericPerson<string, number>();
person.name = 'Jane';
person.getAge = function (x) { return x; };
person.getAge(2);

4. 何时使用泛型

当接口、函数或类要处理多种数据类型并且在其中多个地方使用该类型的时候。

5. 泛型约束

通常用接口来描述约束条件,用extends实现约束。

// 传入符合约束类型的值,必须包含length属性
interface Lengthwise {
    length: number;
}

function loggingIdentity<T extends Lengthwise>(arg: T): T {
    console.log(arg.length); 
    return arg;
}
// 通过 K extends keyof T 确保参数 key 一定是对象中含有的键,这样就不会发生运行时错误
function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] {
  return obj[key];
}

6. 泛型条件类型

条件类型会以一个条件表达式进行类型关系检测,从而在两种类型中选择其一:

T extends U ? X : Y

通常会结合推断关键字infer来使用:

type ParamType<T> = T extends (...args: infer P) => any ? P : T;

这样就可以实现类型抽取,如果T可以赋值给(...args: infer P) => any,那么返回参数P,否则返回T本身的类型。