TypeScript泛型类简介

发布于1/17/2020 来自:「前端知否」微信公众号

创建可重用代码的一种方法是创建代码,让我们在合适的情况下将其用于不同的数据类型。

TypeScript提供了泛型构造来创建可重用的组件,在这里我们可以用一段代码处理各种类型的代码。这使开发人员可以通过输入自己的类型来使用这些组件。

在本文中,我们将研究定义通用类的多种方法,并通过定义接口和extends关键字来验证属性,并通过使用keyof关键字强制代码仅接受对象中的属性名称。

定义泛型类

要在TypeScript中定义通用类,我们将通用类型标记放在类名后的<>之间。我们可以有多个以逗号分隔的类型标记。同样,我们可以使用相同的类型标记来标记函数的类型,以使我们可以在类中更改参数并返回方法的类型。例如,我们可以编写以下代码来使用单个通用类型标记定义通用类:

class GenericPerson<T> {  
name: T;
getName: (name: T) => T;
}

let person = new GenericPerson<string>();
person.name = 'Jane';
person.getName = function(x) { return x; };

在上面的代码中,我们在<>内传入了字符串类型。我们还可以更改为所需的任何其他类型,例如数字,布尔值或以下代码中的任何接口:

class GenericPerson<T> {  
name: T;
getName: (name: T) => T;
}

let person = new GenericPerson<number>();
person.name = 2;
person.getName = function(x) { return x; };

我们还可以在以下代码中的同一类中插入多个类型:

class GenericPerson<T, U> {  
name: T;
getName: (name: U) => U;
}

let person = new GenericPerson<string, number>();
person.name = 'Jane';
person.getName = function (x) { return x; };
person.getName(2);

在上面的代码中,TU可以是或可以不是不同的类型。只要我们将它们都放入并且传递,并使用在类定义中指定的类型分配数据,那么TypeScript编译器将接受它。上面的示例仅在少数情况下有用,因为我们无法在类定义中引用任何属性,因为TypeScript编译器不知道TU包含哪些属性。

为了使TypeScript编译器了解我们的类定义中可能使用的属性,我们可以使用一个接口列出可以与该类一起使用的所有属性,并使用泛型类型标记后的extends关键字表示该属性。类型可能具有界面中列出的属性列表。例如,如果我们想在类方法中使用不同的复杂类型,则可以编写类似以下代码的内容:

interface PersonInterface {
name: string;
age: number;
}

interface GreetingInterface {
toString(): string;
}

class GenericPerson<T extends PersonInterface, U extends GreetingInterface> {
person: T;

greet(greeting: U): string {
return `${greeting.toString()} ${this.person.name}. You're ${this.person.age} years old`;
}
}

let jane = new GenericPerson();

jane.person = {
name: 'Jane',
age: 20
};

console.log(jane.greet('Hi'));

在上面的代码中,我们定义了2个接口,PersonInterfaceGreetingInterface,分别表示可以用T和U引用的属性。在PersonInterface中,我们列出了name和age属性,因此我们可以为T类型的数据引用这些属性。对于U类型的数据,可以对其调用toString方法。因此,在我们的greet方法中,我们可以在问候语上调用toString,因为它具有U类型,而this.person具有T类型,因此可以从中获取nameage属性。

然后,在类定义之后,我们可以实例化该类,然后在所创建的jane对象上为nameage属性设置值。然后,当我们在jane.greet("Hi")上运行console.log时,我们应该看到“ Hi Jane”。您已经20岁了,因为我们为jane.person设置了值。

我们还可以在实例化对象时显式地输入类型,以使类型更加清晰。除了上面的内容,我们可以对其稍作更改并编写以下代码:

interface PersonInterface {
name: string;
age: number;
}

interface GreetingInterface {
toString(): string;
}

class GenericPerson<T extends PersonInterface, U extends GreetingInterface> {
person: T;

greet(greeting: U): string {
return `${greeting.toString()} ${this.person.name}. You're ${this.person.age} years old`;
}
}

let jane = new GenericPerson<PersonInterface, string>();

jane.person = {
name: 'Jane',
age: 20
};

console.log(jane.greet('Hi'));

唯一的区别是,在新的GenericPerson之后添加了<PersonInterface,string>。注意,我们可以在方括号之间添加接口或原始类型。 TypeScript只关心类型是否具有我们定义的接口中列出的方法。现在,类型已受到这些通用类型的约束,我们不必担心引用任何意外的属性。例如,如果我们引用以下代码中不存在的内容:

interface PersonInterface {
name: string;
age: number;
}

interface GreetingInterface {
toString(): string;
}

class GenericPerson<T extends PersonInterface, U extends GreetingInterface> {
person: T;

greet(greeting: U): string {
return `${greeting.foo()} ${this.person.name}. You're ${this.person.age} years old`;
}
}

let jane = new GenericPerson<PersonInterface, string>();

jane.person = {
name: 'Jane',
age: 20
};

console.log(jane.greet('Hi'));

因为我们没有在GreetingInterface中列出foo,所以我们将获得“ U型不存在属性foo。(2339)”。

约束检索对象属性

TypeScript还提供了一种使我们可以使用泛型安全地获取对象属性的方法。我们可以使用keyof关键字来约束对象的值,而不是对象可以承担的另一个对象的键名。例如,我们可以像下面的代码一样使用keyof关键字:

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

let x = { foo: 1, bar: 2, baz: 3 };

console.log(getProperty(x, "foo"));

在上面的代码中,我们约束通用标记K仅接受传递给obj的任何对象的键作为key的有效值,因为KK之后具有extends keyof T。这意味着无论T类型具有什么键,这些键都是K的有效值。使用上面的代码,我们不必担心获取不存在的属性的值。因此,如果我们传入obj中不存在的键名,如以下代码所示:

getProperty(x, "a");

然后,TypeScript编译器将拒绝该代码,并给出错误消息“类型为“ a”的参数不能分配给类型为“ foo”的参数)。 “foo” | “bar”。(2345)”。这意味着,只有'foo','bar'和'baz'是key的有效值,因为它的类型为K,它在T标记后具有extends keyof T标记,以限制只能使用obj的键名。

最后

我们可以使用TypeScript轻松定义泛型类。要在TypeScript中定义通用类,我们将通用类型标记放在类名后的<>之间。我们可以有多个以逗号分隔的类型标记。

同样,我们可以使用相同的类型标记来标记函数的类型,以使我们可以在类中更改参数并返回方法的类型。

我们还可以使用extends关键字来定义可以用通用类型标记的数据引用的属性。我们可以使用keyof关键字将值限制为一个对象可以接受的另一个对象的键名。