typescript-泛型

389 阅读3分钟

在软件工程领域,我们不仅要创建定义一致良好的 API,也需要同时考虑重用性,泛型就给予了这样的灵活性但又不失优雅

泛型之 Hello World

先定义如下的一个函数,这个函数会返回任何传入它的值。

function getValue(a: number): number {
  return a;
}

上面我们定义的函数参数是一个数字类型的,返回值当然也是数字类型的。

假如我们需要传递一个字符串参数并返回,那么就必须再定义一个函数

function getValue(a: number): number {
  return a;
}

function getValue1(a: string): string {
  return a;
}

但是它们两个函数的功能都是一致的,只是参数的类型不一致而已。

而且假如要扩展其他数据类型的话,难道都要再写一次嘛?

或者,我们使用any类型来定义函数:

function getValue(a: any): any {
  return a;
}

但是使用 any 类型就丢失了一些信息:传入的值必须与返回的值是同一种类型。因为 any 体现不出这个关系。

但是 **泛型 ** 可以定义一致的接口,同时考虑代码重用性

这里我们使用 类型变量,它是一种特殊的变量,只表示类型而不是值

字母保持一致就好,一般使用 T 表示 (Type)

function getValue<T>(a: T): T {
  return a;
}
getValue<number>(3);
getValue<string>("qzy");

我们把这个版本的函数叫做泛型,因为它可以适用于多个类型

我们定义了泛型函数后,可以用两种方法使用。

第一种是,传入所有的参数,包含类型参数:

getValue<string>("abc");

第二种方法更普遍。利用了类型推论 -- 即编译器会根据传入的参数自动地帮助我们确定 T 的类型:

getValue(1);

使用泛型变量

使用泛型创建函数时,必须要在函数体内正确的使用这个类型

function getValue<T>(a: T): T {
  console.log(a.length);
  return a;
}

上面的例子中,我们想访问传入参数的 length属性。

但是这个 T 有可能是一个数字类型,而数字类型是不存在 length 属性的。因此编译器就报错了。

function getValue<T>(a: T): T {
  // 类型“T”上不存在属性“length”
  console.log(a.length);
  return a;
}

为此我们定义一个接口来描述条件。

interface LengthProps {
  length: number;
}

function getValue<T extends LengthProps>(a: T): T {
  console.log(a.length);
  return a;
}

getValue([1, 2]); 

getValue(1); // 类型“number”的参数不能赋给类型“LengthProps”的参数

泛型接口

泛型接口有两种方式

第一种:

interface config {
  <T>(val: T): T;
}
// fn: config
// function 后面要与接口定义的一致
let fn: config = function <T>(val: T): T {
  return val;
};

fn(5);

第二种:

interface config<T> {
  (val: T): T;
}

function configFn<T>(val: T): T {
  return val;
}

// config<string>
// = configFn
let fn: config<string> = configFn;

fn("qzy");

泛型约束-类型参数

你可以声明一个类型参数,且它被另一个类型参数所约束。

比如,现在我们想要用属性名从对象里获取这个属性,并且我们想要确保这个属性存在于对象 obj上,因此我们需要在这两个类型之间使用约束。

function getProperty<T, K>(obj: T, key: K) {
  // 类型“K”无法用于索引类型“T”
  return obj[key];
}

let x = { a: 1, b: 2, c: 3, d: 4 };
getProperty(x,'e');

我们代码的意图是,通过传入一个泛型的对象,或者什么其他的变量,然后再传入另外一个泛型的变量,找到 obj 下面的 key 属性。

这个时候就你的编辑器就会报这样的错,我们传入的泛型key变量,不一定是存在于泛型obj中的属性。

为解决这一问题,我们可以使用 keyof

function getProperty<T, K extends keyof T>(obj: T, key: K) {
  // 类型“K”无法用于索引类型“T”
  return obj[key];
}

let x = { a: 1, b: 2, c: 3, d: 4 };

getProperty(x, "b");

getProperty(x,'e'); // 类型“"e"”的参数不能赋给类型“"b" | "a" | "c" | "d"”的参数

K extends keyof T 将两个类型变量关系起来