百分百源码网-让建站变得如此简单! 登录 注册 签到领金币!

主页 | 如何升级VIP | TAG标签

当前位置: 主页>网站教程>JS教程> 理解TypeScript中泛型(Generics)的概念和用途
分享文章到:

理解TypeScript中泛型(Generics)的概念和用途

发布时间:01/01 来源:未知 浏览: 关键词:

本文介绍TypeScript中泛型(Generics)的概念和用途,它为什么重要,及其使用处景。我们会以一些清楚的例子,介绍其语法,类型和怎样构建参数。你可以在你的集成开发环境中跟着实践。

预备工作

要从本文中跟着学习的话,你需要在电脑上预备以下东西:

  • 安置Node.js:你可以运转命令行检查Node可否安置好了。
node -v
  • 安置Node Package Manager: 平常安置Node时,它会顺带安置好所需版本的NPM。
  • 安置TypeScript:假如你安置好了Node Package Manager,你可以用以下命令在本机的全局环境安置TypeScript。
  • npm install -g typescript
    集成开发环境:本文将使用微软团队开发的Visual Studio Code。可以在这里下载。进入其下载的名目,并依照提醒停止安置。记得选中“增加翻开代码”(Add open with code)选项,这样你就可以在本机从任何位置轻松翻开VS Code了。

本文是写给各层次的TypeScript开发人员的,包罗但并不只是初学者。 这里给出了设定工作环境的步骤,是为了照料那些TypeScript和Visual Studio Code的新手们。

TypeScript里的泛型是个啥

在TypeScript中,泛型是一种创立可复用代码组件的工具。这种组件不只能被一品种型使用,而是能被多品种型复用。相似于参数的作用,泛型是一种用以增强类(classes)、类型(types)和接口(interfaces)能力的非常可靠的手段。这样,我们开发者,就可以轻松地将那些可复用的代码组件,适用于各种输入。然而,不要把TypeScript中的泛型错当做any类型来使用——你会在后面看到这两者的不一样。

相似C#和Java这种说话,在它们的工具箱里,泛型是创立可复用代码组件的主要手段之一。即,用于创立一个适用于多品种型的代码组件。这同意会员以他们本人的类使用该泛型组件。

在VSCode中配置TypeScript

在运算机中创立一个新文件夹,然后使用VS Code 翻开它(假如你跟着从头开端操纵,那你已经安置好了)。

在VS Code中,创立一个app.ts文件。我的TypeScript代码都会放在这里面。

把下面打日志的代码拷贝到编纂器中:

console.log("hello TypeScript");

按下F5键,你会看到一个像这样的launch.json文件:

{
  // Use IntelliSense to learn about possible attributes.
  // Hover to view descriptions of existing attributes.
  // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
  "version": "0.2.0",
  "configurations": [
    {
      "type": "node",
      "request": "launch",
      "name": "TypeScript",
      "program": "${workspaceFolder}\\app.ts",
      "outFiles": [
        "${workspaceFolder}/**/*.js"
      ]
    }
  ]
}

里面的name字段的值,原本是Launch Program,我把它改成了TypeScript。你可以把它改成其他值。

点击Terminal Tab,选中Run Tasks,再选中一个Task Runner:"TypeScript Watch Mode",然后会弹出一个tasks.json文件,把它改成下面像这样:

 {
  // See https://go.microsoft.com/fwlink/?LinkId=733558
  // for the documentation about the tasks.json format
  "version": "2.0.0",
  "tasks": [
  {
   "label": "echo",
   "type": "shell",
   "command": "tsc",
   "args": ["-w", "-p","."],
   "problemMatcher": [
    "$tsc-watch"
    ],
   "isBackground": true
   }
  ]
 }

app.ts所在的名目,创立另一个文件tsconfig.json。把下面的代码拷贝进去:

{
  "compilerOptions": {
    "sourceMap": true
  }
}

这样,Task Runner就可以把TypeScript编译成JavaScript,并且可监听到文件的转变,实时编译。

再次点击Ternimal标签,选中Run Build Task,再选中tsc: watch - tsconfig.json,可以看到终端显现的信息:

[21:41:31] Starting compilation in watch mode…

你可以使用VS Code的调试功效编译TypeScript文件。  

1.png

设定好了开发环境,你就可以着手处置TypeScript泛型概念相关的问题了。

寻到问题

TypeScript中不倡议使用any类型,缘由有几点,你可以在本文看到。其中一个缘由,就是调试时缺乏完全的信息。而选中VS Code作为开发工具的一个很好的理由,就是它带来的基于这些信息的智能感知。

假如你有一个类,储备着一个汇合。有办法向该汇合里增加东西,也有办法通过索引猎取汇合里的东西。像这样:

class Collection {
  private _things: string[];
  constructor() {
    this._things = [];
  }
  add(something: string) {
    this._things.push(something);
  }
  get(index: number): string {
    return this._things[index];
  }
}

你可以很快辨识出,此汇合被显示定义为一个string类型的汇合,明显是不克不及在其中使用number的。假如想要处置number的话,可以创立一个接受number而不是string的汇合。着是一个不错的选中,但有一个很大的缺陷——代码反复。代码反复,终究会致使编写和调试代码的时间增多,并且落低内存的使用效力。

另一个选中,是使用any类型代替string类型定义方才的类,像下面这样:

class Collection {
  private _things: any[];
  constructor() {
    this._things = [];
  }
  add(something: any) {
    this._things.push(something);
  }
  get(index: number): any {
    return this._things[index];
  }
}

此时,该汇合支撑你给出的任何类型。假如你创立像这样的逻辑构建此汇合的话:

let Stringss = new Collection();
Stringss.add("hello");
Stringss.add("world");

这增加了字符串"hello"和"world"到汇合中,你可以打出像length这样的属性,返回任意一个汇合元素的长度。  

console.log(Stringss.get(0).length);

字符串"hello"有五个字符,运转TypeScript代码,你可以在调试模式下看到它。  

2.png

请留意,当你鼠标悬停在length属性上时,VS Code的智能感知没有供给任何信息,由于它不知道你选中使用确实切类型。当你像下面这样,把其中一个增加的元素修改为其他类型时,比方number,这种不克不及被智能感知到的状况会表现得愈加明显:

let Strings = new Collection();
Strings.add(001);
Strings.add("world");
console.log(Strings.get(0).length);

你打出一个undefined的结果,依然没有什么有用信息。假如你更进一步,决议打印string的子字符串——它会报运转时错误,但不指不出任何详细的内容,更重要的是,编译器没有给出任何类型不匹配的编译时错误。  

console.log(Stringss.get(0).substr(0,1));

3.png

这仅仅是使用any类型定义该汇合的一种后果罢了。

懂得中心思想

方才使用any类型致使的问题,可以用TypeScript中的泛型来解决。其中心思想是类型平安。使用泛型,你可以用一种编译器能懂得的,并且符合我们推断的方式,指定类、类型和接口的实例。正如在其他强类型说话中的状况一样,用这种办法,就可以在编译时发明你的类型错误,从而包管了类型平安。

泛型的语法像这样:

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

你可以在此前创立的汇合中使用泛型,用尖括号括起来。  

class Collection<T> {
  private _things: T[];
  constructor() {
    this._things = [];
  }
  add(something: T): void {
    this._things.push(something);
  }
  get(index: number): T {
    return this._things[index];
  }
}
let Stringss = new Collection<String>();
Stringss.add(001);
Stringss.add("world");
console.log(Stringss.get(0).substr(0, 1));

假如将带有尖括号的新逻辑复制到代码编纂器中,你会马上留意到"001"下的波浪线。这是由于,TypeScript此刻可以从指定的泛型类型推断出001不是字符串。在T显现的地方,就可以使用string类型,这就实现了类型平安。本质上,这个汇合的输出可以是任何类型,但你指明了它应当是string类型,所以编译器推断它就是string类型。这里使用的泛型声明是在类级别,它也可以在其他级别定义,如静态办法级别和实例办法级别,你稍后会看到。

使用泛型

你可以在泛型声明中,包括多个类型参数,它们只需要用逗号分隔,像这样:

class Collection<T, K> {
  private _things: K[];
  constructor() {
    this._things = [];
  }
  add(something: K): void {
    this._things.push(something);
  }
  get(index: number): T {
    console.log(index);
  }
}

声明时,类型参数也可以在函数中显式使用,比方:

class Collection {
  private _things: any[];
  constructor() {
    this._things = [];
  }
  add<A>(something: A): void {
    this._things.push(something);
  }
  get<B>(index: number): B {
    return this._things[index];
  }
}

因此,当你要创立一个新的汇合时,在办法级别声明的泛型,此刻也会在办法调取级别中被指示,像这样:  

let Stringss = new Collection();
Stringss.add<string>("hello");
Stringss.add("world");

你还可留意到,在鼠标悬停时,VS Code智能感知能够推断出第二个add函数调取依然是string类型。

泛型声明一样适用于静态办法:

static add<A>(something: A): void {
  _things.push(something);
}

虽然初始化静态办法时,可使用泛型类型,但是,对初始化静态属性则不克不及。

泛型束缚

此刻,你已经对泛型有比力好的认识,是时候提到泛型的中心缺陷及其有用的解决方案了。使用泛型,很多属性的类型都能被TypeScript推断出来,然而,在某些TypeScript不克不及做出准确推断的地方,它不会做任何假设。为了类型平安,你需要将这些要求或者束缚定义为接口,并在泛型初始化中继承它们。

假如你有这样一个非常简便的函数:

function printName<T>(arg: T) {
  console.log(arg.length);
  return arg;
}
printName(3);

由于TypeScript没法推断出arg参数是啥类型,不克不及证明所有类型都具有length属性,因此不克不及假设它是一个字符串(具有length属性)。所以,你会在length属性下看到一条波浪线。如前所述,你需要创立一个接口,让泛型的初始化可以继承它,以便编译器不再报警。  

interface NameArgs {
  length: number;
}

你可以在泛型声明中继承它:

function printName<T extends NameArgs>(arg: T) {
  console.log(arg.length);
  return arg;
}

这告诉TypeScript,可使用任何具有length属性的类型。 定义它之后,函数调取语句也必需更换,由于它不再适用于所有类型。 所以它应看起来是这样:

printName({length: 1, value: 3});

这是一个很根基的例子。但懂得了它,你就能看到在使用泛型时,设定泛型束缚是多么有用。

为什么是泛型

一个活泼于Stack Overflow社区的成员,Behrooz,在后续内容中很好的答复了这个问题。在TypeScript中使用泛型的主要缘由是使类型,类或接口充当参数。 它帮忙我们为不一样类型的输入重用雷同的代码,由于类型本身可用作参数。

泛型的一些好处有:

  • 定义输入和输出参数类型之间的关系。比方
function test<T>(input: T[]): T {
  //… 
}

同意你确保输入和输出使用雷同的类型,尽管输入是用的数组。

  • 可使用编译时更强大的类型检查。在上诉示例中,编译器让你知道数组办法可用于输入,任何其他办法则不可。
  • 你可以去除不需要的强迫类型转换。比方,假如你有一个常量列表:
Array<Item> a = [];

变量数组时,你可以由智能感知拜访到Item类型的所有成员。

其他资源

  • 官方文档

结论

你已经看完了泛型概念的概述,并看到了各种示例来帮忙揭示它背后的思想。 起初,泛型的概念大概令人困惑,我倡议,把本文再读一遍,并查阅本文所供给的额外资源,帮忙本人更好地懂得。泛型是一个很棒的概念,可以帮忙我们在JavaScript中,更好地操纵输入和输出。请欢乐地编码吧!

打赏

打赏

取消

感谢您的支持,我会继续努力的!

扫码支持
扫码打赏,你说多少就多少

打开支付宝扫一扫,即可进行扫码打赏哦

百分百源码网 建议打赏1~10元,土豪随意,感谢您的阅读!

共有151人阅读,期待你的评论!发表评论
昵称: 网址: 验证码: 点击我更换图片
最新评论

本文标签

广告赞助

能出一分力是一分吧!

订阅获得更多模板

本文标签

广告赞助

订阅获得更多模板