十分钟教你理解TypeScript中的泛型
<blockquote><p>转载请注明出处:葡萄城官网,葡萄城为开发者提供专业的开发工具、解决方案和服务,赋能开发者。<br>原文出处:https://blog.bitsrc.io/understanding-generics-in-typescript-1c041dc37569</p>
</blockquote>
<p><img src="https://img2018.cnblogs.com/blog/139239/201907/139239-20190711145116037-11212706.png" alt="" width="1170" height="522"></p>
<h3 id="你将学到什么" data-source-line="7">你将在本文中学到什么</h3>
<p data-source-line="9">本文介绍TypeScript中泛型(Generics)的概念和用法,它为什么重要,及其使用场景。我们会以一些清晰的例子,介绍其语法,类型和如何构建参数。你可以在你的集成开发环境中跟着实践。</p>
<h3 id="准备工作" data-source-line="11">准备工作</h3>
<p data-source-line="12">要从本文中跟着学习的话,你需要在电脑上准备以下东西:</p>
<ul data-source-line="14">
<li>安装Node.js:你可以运行命令行检查Node是否安装好了。</li>
</ul>
<div class="cnblogs_Highlighter">
<pre class="brush:javascript;gutter:true;">node -v</pre>
</div>
<ul data-source-line="23">
<li>安装Node Package Manager: 通常安装Node时,它会顺带安装好所需版本的NPM。</li>
<li>安装TypeScript:如果你安装好了Node Package Manager,你可以用以下命令在本机的全局环境安装TypeScript。</li>
<li>
<div class="cnblogs_Highlighter">
<pre class="brush:javascript;gutter:true;">npm install -g typescript</pre>
</div>
集成开发环境:本文将使用微软团队开发的Visual Studio Code。可以在这里下载。进入其下载的目录,并按照提示进行安装。记得选择“添加打开代码”(Add open with code)选项,这样你就可以在本机从任何位置轻松打开VS Code了。</li>
</ul>
<p data-source-line="25">本文是写给各层次的TypeScript开发人员的,包括但并不只是初学者。 这里给出了设置工作环境的步骤,是为了照顾那些TypeScript和Visual Studio Code的新手们。</p>
<h3 id="typescript里的泛型是个啥" data-source-line="27">TypeScript里的泛型是个啥</h3>
<p data-source-line="28">在TypeScript中,泛型是一种创建可复用代码组件的工具。这种组件不只能被一种类型使用,而是能被多种类型复用。类似于参数的作用,泛型是一种用以增强类(classes)、类型(types)和接口(interfaces)能力的非常可靠的手段。这样,我们开发者,就可以轻松地将那些可复用的代码组件,适用于各种输入。然而,不要把TypeScript中的泛型错当成<code>any</code>类型来使用——你会在后面看到这两者的不同。</p>
<p data-source-line="30">类似C#和Java这种语言,在它们的工具箱里,泛型是创建可复用代码组件的主要手段之一。即,用于创建一个适用于多种类型的代码组件。这允许用户以他们自己的类使用该泛型组件。</p>
<h3 id="在vs-code中配置typescript" data-source-line="32">在VS Code中配置TypeScript</h3>
<p data-source-line="33">在计算机中创建一个新文件夹,然后使用VS Code 打开它(如果你跟着从头开始操作,那你已经安装好了)。</p>
<p data-source-line="35">在VS Code中,创建一个<code>app.ts</code>文件。我的TypeScript代码都会放在这里面。</p>
<p data-source-line="37">把下面打日志的代码拷贝到编辑器中:</p>
<div class="cnblogs_Highlighter">
<pre class="brush:javascript;gutter:true;">console.log("hello TypeScript");</pre>
</div>
<p data-source-line="43">按下<code>F5</code>键,你会看到一个像这样的<code>launch.json</code>文件:</p>
<div class="cnblogs_Highlighter">
<pre class="brush:javascript;gutter:true;">{
// 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"
]
}
]
}
</pre>
</div>
<p data-source-line="65">里面的<code>name</code>字段的值,本来是<code>Launch Program</code>,我把它改成了<code>TypeScript</code>。你可以把它改成其他值。</p>
<p data-source-line="67">点击<code>Terminal Tab</code>,选择<code>Run Tasks</code>,再选择一个<code>Task Runner</code>:"TypeScript Watch Mode",然后会弹出一个<code>tasks.json</code>文件,把它改成下面像这样:</p>
<div class="cnblogs_Highlighter">
<pre class="brush:javascript;gutter:true;"> {
// 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
}
]
}
</pre>
</div>
<p data-source-line="89">在<code>app.ts</code>所在的目录,创建另一个文件<code>tsconfig.json</code>。把下面的代码拷贝进去:</p>
<div class="cnblogs_Highlighter">
<pre class="brush:javascript;gutter:true;">{
"compilerOptions": {
"sourceMap": true
}
}
</pre>
</div>
<p data-source-line="99">这样,<code>Task Runner</code>就可以把TypeScript编译成JavaScript,并且可监听到文件的变化,实时编译。</p>
<p data-source-line="101">再次点击<code>Ternimal</code>标签,选择<code>Run Build Task</code>,再选择<code>tsc: watch - tsconfig.json</code>,可以看到终端出现的信息:</p>
<div class="cnblogs_Highlighter">
<pre class="brush:javascript;gutter:true;"> Starting compilation in watch mode…
</pre>
</div>
<p>你可以使用VS Code的调试功能编译TypeScript文件。 </p>
<p><img src="https://img2018.cnblogs.com/blog/139239/201907/139239-20190711145257756-261558000.png" alt=""></p>
<p data-source-line="111">设置好了开发环境,你就可以着手处理TypeScript泛型概念相关的问题了。</p>
<h3 id="找到问题" data-source-line="113">找到问题</h3>
<p data-source-line="115">TypeScript中不建议使用<code>any</code>类型,原因有几点,你可以在本文看到。其中一个原因,就是调试时缺乏完整的信息。而选择VS Code作为开发工具的一个很好的理由,就是它带来的基于这些信息的智能感知。</p>
<p data-source-line="117">如果你有一个类,存储着一个集合。有方法向该集合里添加东西,也有方法通过索引获取集合里的东西。像这样:</p>
<div class="cnblogs_Highlighter">
<pre class="brush:javascript;gutter:true;">class Collection {
private _things: string[];
constructor() {
this._things = [];
}
add(something: string) {
this._things.push(something);
}
get(index: number): string {
return this._things;
}
}
</pre>
</div>
<p data-source-line="134">你可以很快辨识出,此集合被显示定义为一个<code>string</code>类型的集合,显然是不能在其中使用<code>number</code>的。如果想要处理<code>number</code>的话,可以创建一个接受<code>number</code>而不是<code>string</code>的集合。着是一个不错的选择,但有一个很大的缺点——代码重复。代码重复,最终会导致编写和调试代码的时间增多,并且降低内存的使用效率。</p>
<p data-source-line="136">另一个选择,是使用<code>any</code>类型代替<code>string</code>类型定义刚才的类,像下面这样:</p>
<div class="cnblogs_Highlighter">
<pre class="brush:javascript;gutter:true;">class Collection {
private _things: any[];
constructor() {
this._things = [];
}
add(something: any) {
this._things.push(something);
}
get(index: number): any {
return this._things;
}
}
</pre>
</div>
<p data-source-line="152">此时,该集合支持你给出的任何类型。如果你创建像这样的逻辑构建此集合的话:</p>
<div class="cnblogs_Highlighter">
<pre class="brush:javascript;gutter:true;">let Stringss = new Collection();
Stringss.add("hello");
Stringss.add("world");
</pre>
</div>
<p>这添加了字符串"hello"和"world"到集合中,你可以打出像<code>length</code>这样的属性,返回任意一个集合元素的长度。 </p>
<div class="cnblogs_Highlighter">
<pre class="brush:javascript;gutter:true;">console.log(Stringss.get(0).length);
</pre>
</div>
<p>字符串"hello"有五个字符,运行TypeScript代码,你可以在调试模式下看到它。 </p>
<p><img src="https://img2018.cnblogs.com/blog/139239/201907/139239-20190711145402835-1118876491.png" alt=""></p>
<p>请注意,当你鼠标悬停在length属性上时,VS Code的智能感知没有提供任何信息,因为它不知道你选择使用的确切类型。当你像下面这样,把其中一个添加的元素修改为其他类型时,比如<code>number</code>,这种不能被智能感知到的情况会体现得更加明显:</p>
<div class="cnblogs_Highlighter">
<pre class="brush:javascript;gutter:true;">let Strings = new Collection();
Strings.add(001);
Strings.add("world");
console.log(Strings.get(0).length);
</pre>
</div>
<p>你打出一个<code>undefined</code>的结果,仍然没有什么有用信息。如果你更进一步,决定打印<code>string</code>的子字符串——它会报运行时错误,但不指不出任何具体的内容,更重要的是,编译器没有给出任何类型不匹配的编译时错误。 </p>
<div class="cnblogs_Highlighter">
<pre class="brush:javascript;gutter:true;">console.log(Stringss.get(0).substr(0,1));
</pre>
</div>
<p><img src="https://img2018.cnblogs.com/blog/139239/201907/139239-20190711145433850-174101241.png" alt=""></p>
<p data-source-line="186">这仅仅是使用<code>any</code>类型定义该集合的一种后果罢了。</p>
<h3 id="理解中心思想" data-source-line="188">理解中心思想</h3>
<p data-source-line="189">刚才使用<code>any</code>类型导致的问题,可以用TypeScript中的泛型来解决。其中心思想是类型安全。使用泛型,你可以用一种编译器能理解的,并且合乎我们判断的方式,指定类、类型和接口的实例。正如在其他强类型语言中的情况一样,用这种方法,就可以在编译时发现你的类型错误,从而保证了类型安全。</p>
<p data-source-line="191">泛型的语法像这样:</p>
<div class="cnblogs_Highlighter">
<pre class="brush:javascript;gutter:true;">function identity<T>(arg: T): T {
return arg;
}
</pre>
</div>
<p>你可以在之前创建的集合中使用泛型,用尖括号括起来。 </p>
<div class="cnblogs_Highlighter">
<pre class="brush:javascript;gutter:true;">class Collection<T> {
private _things: T[];
constructor() {
this._things = [];
}
add(something: T): void {
this._things.push(something);
}
get(index: number): T {
return this._things;
}
}
let Stringss = new Collection<String>();
Stringss.add(001);
Stringss.add("world");
console.log(Stringss.get(0).substr(0, 1));
</pre>
</div>
<p data-source-line="219">如果将带有尖括号的新逻辑复制到代码编辑器中,你会立即注意到"001"下的波浪线。这是因为,TypeScript现在可以从指定的泛型类型推断出001不是字符串。在<code>T</code>出现的地方,就可以使用<code>string</code>类型,这就实现了类型安全。本质上,这个集合的输出可以是任何类型,但你指明了它应该是<code>string</code>类型,所以编译器推断它就是<code>string</code>类型。这里使用的泛型声明是在类级别,它也可以在其他级别定义,如静态方法级别和实例方法级别,你稍后会看到。</p>
<h3 id="使用泛型" data-source-line="221">使用泛型</h3>
<p data-source-line="222">你可以在泛型声明中,包含多个类型参数,它们只需要用逗号分隔,像这样:</p>
<div class="cnblogs_Highlighter">
<pre class="brush:javascript;gutter:true;">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);
}
}
</pre>
</div>
<p data-source-line="238">声明时,类型参数也可以在函数中显式使用,比如:</p>
<p> </p>
<div class="cnblogs_Highlighter">
<pre class="brush:javascript;gutter:true;">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;
}
}
</pre>
</div>
<p>因此,当你要创建一个新的集合时,在方法级别声明的泛型,现在也会在方法调用级别中被指示,像这样: </p>
<div class="cnblogs_Highlighter">
<pre class="brush:javascript;gutter:true;">let Stringss = new Collection();
Stringss.add<string>("hello");
Stringss.add("world");
</pre>
</div>
<p data-source-line="260">你还可注意到,在鼠标悬停时,VS Code智能感知能够推断出第二个add函数调用仍然是<code>string</code>类型。</p>
<p data-source-line="262">泛型声明同样适用于静态方法:</p>
<div class="cnblogs_Highlighter">
<pre class="brush:javascript;gutter:true;">static add<A>(something: A): void {
_things.push(something);
}
</pre>
</div>
<p data-source-line="269">虽然初始化静态方法时,可使用泛型类型,但是,对初始化静态属性则不能。</p>
<h3 id="泛型约束" data-source-line="271">泛型约束</h3>
<p data-source-line="272">现在,你已经对泛型有比较好的认识,是时候提到泛型的核心缺点及其实用的解决方案了。使用泛型,许多属性的类型都能被TypeScript推断出来,然而,在某些TypeScript不能做出准确推断的地方,它不会做任何假设。为了类型安全,你需要将这些要求或者约束定义为接口,并在泛型初始化中继承它们。</p>
<p data-source-line="274">如果你有这样一个非常简单的函数:</p>
<div class="cnblogs_Highlighter">
<pre class="brush:javascript;gutter:true;">function printName<T>(arg: T) {
console.log(arg.length);
return arg;
}
printName(3);
</pre>
</div>
<p>因为TypeScript无法推断出<code>arg</code>参数是什么类型,不能证明所有类型都具有<code>length</code>属性,因此不能假设它是一个字符串(具有<code>length</code>属性)。所以,你会在<code>length</code>属性下看到一条波浪线。如前所述,你需要创建一个接口,让泛型的初始化可以继承它,以便编译器不再报警。 </p>
<div class="cnblogs_Highlighter">
<pre class="brush:javascript;gutter:true;">interface NameArgs {
length: number;
}
</pre>
</div>
<p data-source-line="292">你可以在泛型声明中继承它:</p>
<div class="cnblogs_Highlighter">
<pre class="brush:javascript;gutter:true;">function printName<T extends NameArgs>(arg: T) {
console.log(arg.length);
return arg;
}
</pre>
</div>
<p>这告诉TypeScript,可使用任何具有<code>length</code>属性的类型。 定义它之后,函数调用语句也必须更改,因为它不再适用于所有类型。 所以它应看起来是这样: </p>
<p> </p>
<div class="cnblogs_Highlighter">
<pre class="brush:javascript;gutter:true;">printName({length: 1, value: 3});
</pre>
</div>
<p data-source-line="307">这是一个很基础的例子。但理解了它,你就能看到在使用泛型时,设置泛型约束是多么有用。</p>
<h3 id="为什么是泛型" data-source-line="309">为什么是泛型</h3>
<p data-source-line="310">一个活跃于Stack Overflow社区的成员,Behrooz,在后续内容中很好的回答了这个问题。在TypeScript中使用泛型的主要原因是使类型,类或接口充当参数。 它帮助我们为不同类型的输入重用相同的代码,因为类型本身可用作参数。</p>
<p data-source-line="312">泛型的一些好处有:</p>
<ul data-source-line="314">
<li>定义输入和输出参数类型之间的关系。比如</li>
</ul>
<p> </p>
<div class="cnblogs_Highlighter">
<pre class="brush:javascript;gutter:true;">function test<T>(input: T[]): T {
//…
}
</pre>
</div>
<p> </p>
<p data-source-line="320">允许你确保输入和输出使用相同的类型,尽管输入是用的数组。</p>
<ul data-source-line="322">
<li>可使用编译时更强大的类型检查。在上诉示例中,编译器让你知道数组方法可用于输入,任何其他方法则不行。</li>
<li>你可以去掉不需要的强制类型转换。比如,如果你有一个常量列表:</li>
</ul>
<div class="cnblogs_Highlighter">
<pre class="brush:javascript;gutter:true;">Array<Item> a = [];
</pre>
</div>
<p data-source-line="327">变量数组时,你可以由智能感知访问到Item类型的所有成员。</p>
<h3 id="其他资源" data-source-line="329">其他资源</h3>
<ul data-source-line="330">
<li>官方文档</li>
</ul>
<h3 id="结论" data-source-line="332">结论</h3>
<p data-source-line="334">你已经看完了泛型概念的概述,并看到了各种示例来帮助揭示它背后的思想。 起初,泛型的概念可能令人困惑,我建议,把本文再读一遍,并查阅本文所提供的额外资源,帮助自己更好地理解。泛型是一个很棒的概念,可以帮助我们在JavaScript中,更好地控制输入和输出。请快乐地编码吧!</p>
</div>
<div id="MySignature" role="contentinfo">
<hr>
<br>
<p style="font-size: 16px; font-family: 微软雅黑, 黑体, Arial; color: #000">本文是由葡萄城技术开发团队发布,转载请注明出处:葡萄城官网</p>
<!--p style="font-size: 16px; font-family: 微软雅黑, 黑体, Arial; color: #000">了解企业级低代码开发平台,请前往活字格
</p><p style="font-size: 16px; font-family: 微软雅黑, 黑体, Arial; color: #000">了解可嵌入您系统的在线 Excel,请前往SpreadJS纯前端表格控件</p>
<p style="font-size: 16px; font-family: 微软雅黑, 黑体, Arial; color: #000">了解嵌入式的商业智能和报表软件,请前往Wyn Enterprise
</p-->
<br><br><br>
来源:https://www.cnblogs.com/powertoolsteam/p/11170031.html
頁:
[1]