七爪源码:在 TypeScript 中学习泛型

介绍

你知道 TypeScript 中的泛型吗? 你能向其他人解释一下吗?很难调和类型安全性和代码通用性。 如果您尝试对所有类型使用相同的代码,则会牺牲类型安全。 另一方面,如果你试图强调类型安全,你将不得不大量生产类似的代码,难以实现代码的通用性。 泛型是为解决这些问题而引入的一种语言特性。 泛型可用于实现类型安全和代码通用性。

泛型解决的问题

现在让我们看看泛型解决的问题。 这是 chooseRandomlyString() 函数。 此函数将两个字符串作为参数,并返回以 50-50 概率绘制的第一个或第二个参数的值。

function chooseRandomlyString(v1: string, v2: string): string {
  return Math.random() <= 0.5 ? v1 : v2;
}

chooseRandomlyString 函数只能在绘制字符串时复用该函数。

const winOrLose = chooseRandomlyString("win", "lose");

此外,由于 50-50 抽签的逻辑是通用的,假设我们决定为 URL 对象创建一个实现,用于广告的 A/B 测试。

function chooseRandomlyURL(v1: URL, v2: URL): URL {
  return Math.random() <= 0.5 ? v1 : v2;
}
const url: URL = chooseRandomlyURL(urlA, urlB);

到目前为止,chooseRandomly() 函数已经重复了两次,创建了三个相同的函数,它们仅在参数类型上有所不同。

function chooseRandomlyString(v1: string, v2: string): string {
  return Math.random() <= 0.5 ? v1 : v2;
}
function chooseRandomlyNumber(v1: number, v2: number): number {
  return Math.random() <= 0.5 ? v1 : v2;
}
function chooseRandomlyURL(v1: URL, v2: URL): URL {
  return Math.random() <= 0.5 ? v1 : v2;
}

那么我们如何才能使代码通用呢? 第一种可能的方法是使类型为 any。 这种方法的问题在于,由于返回类型也是 any,因此编译器不会对其进行检查,这可能会导致错误。 换句话说,类型安全受到损害。

在下面的示例代码中,将数字类型传递给 chooseRandomly(),但返回值被视为字符串类型。 这段代码不会导致编译错误,但是如果你运行编译后的代码,你会在第 5 行看到错误 TypeError: str.toLowerCase is not a function。

function chooseRandomly(v1: any, v2: any): any {
  return Math.random() <= 0.5 ? v1 : v2;
}

let str = chooseRandomly(0, 1); // ERROR
str = str.toLowerCase();

我们如何才能同时实现代码通用性和类型安全? 这就是泛型派上用场的地方。 泛型背后的想法实际上非常简单:“让类型像变量一样对待”。 这是什么意思? 让我们从“哪些部分彼此不同?”的角度来看看前面讨论的三个重复函数。 让我们从“哪个部分彼此不同? 您会注意到用 <> 突出显示的部分不同,如下所示。 除此之外,代码完全相同。

// NOTE: THIS PROGRAM OUTPUTS ERROR.
function chooseRandomly(v1: , v2: ):  {
  return Math.random() <= 0.5 ? v1 : v2;
}function chooseRandomly(v1: , v2: ):  {
  return Math.random() <= 0.5 ? v1 : v2;
}function chooseRandomly(v1: , v2: ):  {
  return Math.random() <= 0.5 ? v1 : v2;
}chooseRandomly("win", "lose");
chooseRandomly(1, 2);
chooseRandomly(urlA, urlB);

这些不同的部分中的每一个都与一种类型有关。 如果您想将此部分视为变量,即使不了解泛型的语法,程序员也可能会想象以下代码。

// NOTE: THIS PROGRAM OUTPUTS ERROR.
function chooseRandomly(v1: , v2: ):  {
  return Math.random() <= 0.5 ? v1 : v2;
}chooseRandomly("win", "lose");
chooseRandomly(1, 2);
chooseRandomly(urlA, urlB);

替换的部分代表“类型参数”。 在这个例子中,类型也是一个参数,就像值参数一样,所以当调用 chooseRandomly() 函数时,类型被传递给函数,就像在 chooseRandomly 中一样。 这是将类型视为参数的代码的诞生。 我已经解释过“泛型是将类型视为变量。

上面的代码只是我为了理解泛型的概念而编造的一个虚构代码。 既然不能按原样理解 TypeScript,我们就用 TypeScript 泛型的语法重写吧。 它与虚构的代码相差不远。 写下以下内容。

function chooseRandomly(v1: T, v2: T): T {
  return Math.random() <= 0.5 ? v1 : v2;
}

chooseRandomly("win", "lose");
chooseRandomly(1, 2);
chooseRandomly(urlA, urlB);

chooseRandomly 中的 是类型变量名的定义。 作为惯例,经常使用 T,但它可以是 A 或 Type。 A 和 T 被写为函数参数类型或返回类型是指类型变量。

让我们使用泛化的 chooseRandomly 来处理我们在之前编译时没有注意到的错误代码。 然后,“0”类型的编译错误参数不可分配给“字符串”类型的参数,您现在可以注意到一个错误,您在应该放置类型字符串的地方分配了 0。

泛型是一种提供代码通用性和类型安全性的语言特性。 当您想让泛型代码可用于各种类型时,请考虑使用泛型。


泛型的另一个简单的具体例子

班级

类也可以通过传递类型参数(如泛型函数)来实现泛型。 类型参数 T 在整个类中用作方法的返回类型和参数类型。

class Sample {
  item: T;

  constructor(item: T) {
    this.item = item;
  }

  getItem(): T {
    return this.item;
  }
}

let strObj = new Sample("ONE");
strObj.getItem(); //=> "ONE"

let numObj = new Sample(1);
numObj.getItem(); //=> 1


界面

在这里,接口也可以以与上述泛型函数和类相同的方式创建。

interface KeyValue {
    key: T;
    value: U;
}

let obj: KeyValue = { key: "TWO", value: 2 } //= {key: "TWO", value: 2}


类型参数的约束

到目前为止介绍的通用类型参数已经接受任何类型的参数。 但是,在某些情况下,您可能希望将参数中接受的值限制为仅某些类型。

例如,在下面的示例中,我们试图获取 arg 的属性名称,但编译器会警告我们,因为并非所有类型都有名称。

function getName(arg: T): string {   
    return arg.name; // Property 'name' does not exist on type 'T'. 
}

在这种情况下,您可以通过编写以下内容来指定 T 必须是满足 exteds 中指定的接口的类型。 这也避免了实现过程中的编译器错误。 如果您在调用函数时传递违反约束的参数,它也会给出错误。

interface argTypes {
  name: string;
}

function getName(arg: T): string {
  return arg.name;
}

getName({ name: "author2000" });

关注七爪网,获取更多APP/小程序/网站源码资源!

发表评论
留言与评论(共有 0 条评论) “”
   
验证码:

相关文章

推荐文章