TypeScript通用性的代码示例

54 阅读4分钟

TypeScript中的泛型在刚开始接触TS时并不容易理解。就我个人而言,我在开始时也有过挣扎,然而,一旦你掌握了它们的使用方法,它们会让你成为一个更完整的TypeScript开发者。

在这个TypeScript教程中,你将学习如何在TypeScript中使用泛型。我们将首先定义一个JavaScript箭头表达式(也叫箭头函数),它接收一个对象(这里是:dog )并返回其age 属性:

const getAge = (dog) => {  return dog.age;};

然后,我们将用一个具有这个所需属性的对象来调用这个函数:

const trixi = {  name: 'Trixi',  age: 7,};
console.log(getAge(trixi));// 7

现在,如果我们想在TypeScript中定义这段代码,它将会有以下变化:

type Dog = {  name: string;  age: number;};
const getAge = (dog: Dog) => {  return dog.age;};
const trixi: Dog = {  name: 'Trixi',  age: 7,};
console.log(getAge(trixi));// 7

然而,这个函数现在只针对一个TypeScript类型(这里:Dog )。如果我们使用不同类型的值作为参数(例如:Person ),就会出现TypeScript错误,因为两种类型的结构不同:

type Person = {  firstName: string;  lastName: string;  age: number;};
const robin: Person = {  firstName: 'Robin',  lastName: 'Wieruch',  age: 7,};
console.log(getAge(robin));// Argument of type 'Person' is not assignable to parameter of type 'Dog'.//   Property 'name' is missing in type 'Person' but required in type 'Dog'.

arrow函数期望一个Dog类型的参数,因为它在函数签名中被定义了,但在前面的例子中,它收到了一个Person类型的参数,它有不同的属性(尽管两者都共享age 属性):

const getAge = (dog: Dog) => {  return dog.age;};

除了给参数一个更抽象但描述性的名字外,一个解决方案是使用TypeScript联合类型:

const getAge = (mammal: Dog | Person) => {  return mammal.age;};

对于大多数TypeScript项目来说,这种解决方案是可行的。然而,一旦项目规模扩大(纵向和横向),你肯定会遇到对TypeScript泛型的需求,因为函数应该接受任何泛型(你也可以读作:抽象)类型,它仍然满足某些要求(这里:有一个age 属性)。

让我们进入TypeScript的泛型 ...

TypeScript中的泛型

一旦项目的规模横向增长(例如,一个项目中有更多的域),像getAge 这样的抽象函数可能会收到两个以上的类型(这里:DogPerson )作参数。总而言之,我们也必须横向扩展联合类型,这很烦人(但仍然可以工作)而且容易出错:

type Mammal = Person | Dog | Cat | Horse;

在正交方向上,一旦一个项目的规模垂直增长,那些越来越多的可重用的、因此也是抽象的函数(如getAge )应该更多的处理通用类型而不是领域特定的类型(如Dog,Person )。

流行的用例。大多数情况下,你会在第三方库中看到这种情况,他们不知道你项目的领域(例如狗,人),但需要预测任何满足某些要求的类型(例如需要的age 属性)。在这里,第三方库不能再使用联合类型作为逃逸的工具,因为它们已经不在实际项目的开发者手中。

总之,如果getAge 函数应该处理任何具有age 属性的实体,它必须是通用的(读作:抽象的)。因此,我们需要使用某种占位符来使用泛型而泛型最常被实现为T:

type Mammal = {  age: number;};
const getAge = <T extends Mammal>(mammal: T) => {  return mammal.age;};

而T extends Mammal代表任何具有age 属性的类型。虽然使用下面的工作:

type Person = {  firstName: string;  lastName: string;  age: number;};
const robin: Person = {  firstName: 'Robin',  lastName: 'Wieruch',  age: 7,};
console.log(getAge(robin));

你现在已经成功使用了一个TypeScript泛型。抽象的getAge() 函数将任何具有age 属性的对象作为参数。忽略了age 属性会给我们一个TypeScript错误:

type Mammal = {  age: number;};
const getAge = <T extends Mammal>(mammal: T) => {  return mammal.age;};
type Person = {  firstName: string;  lastName: string;  age?: number;};
const robin: Person = {  firstName: 'Robin',  lastName: 'Wieruch',  // age: 7,};
console.log(getAge(robin));// Argument of type 'Person' is not assignable to parameter of type 'Mammal'.//   Types of property 'age' are incompatible.//     Type 'number | undefined' is not assignable to type 'number'.//       Type 'undefined' is not assignable to type 'number'

泛型在第三方库中被大量使用。如果第三方库正确地实现了泛型,当你在特定领域的TypeScript应用程序中使用这些抽象库时,你不必过多地考虑它们。