全网最全的,最详细的,最友好的 Typescript 新手教程
共 28306字,需浏览 57分钟
·
2021-04-14 20:08
全网最全的,最详细的,最友好的 Typescript 新手教程,拿走不谢,希望给个点赞,在看,转发,谢谢
本文翻译自
TypeScript tutorial for beginners: who this guide is for
TypeScript新手教程:本指南是给谁看的
下面的指南是一个TypeScript教程,供有兴趣学习更多TypeScript知识的JavaScript开发者使用。这意味着您需要对“普通的”JavaScript有足够的了解,尽管我将在接下来的过程中为您提供一些基本的指导。
单词TypeScript和“初学者”属于同一个教程吗?在写这篇指南之前,我并不确定,但每天我都看到很多初学者对TypeScript感兴趣。如果你决定这样做,要意识到,在你早期的时候,同时学习TypeScript和JavaScript是很难的。但从长远来看,这是值得的。继续前进!如果这是你的情况,欢迎你继续阅读。
在开始之前,请确保系统上安装了最新版本的Node.js。
现在享受阅读吧!
TypeScript初学者教程:什么是TypeScript?
官方网站上的定义是:“JavaScript的类型化超集”,但它假设你知道“超集”是什么,以及“类型化”是什么意思。为了简单起见,你可以把TypeScript看作是JavaScript的“顶层”。
TypeScript是一个层,因为你可以在你的编辑器中编写TypeScript代码。编译之后,所有TypeScript的东西都消失了,剩下的只是简单的JavaScript。
如果编译步骤的概念让您感到困惑,请记住JavaScript已经编译并解释过了。有一个JavaScript引擎读取并执行你的代码。
但是JavaScript引擎不能读取TypeScript代码,所以任何TypeScript文件都要经过“预翻译”过程,也就是编译。只有在第一个编译步骤之后,才剩下纯JavaScript代码,可以在浏览器中运行。稍后你会看到TypeScript是如何编译的。
现在我们要记住,TypeScript是一种特殊的JavaScript,但在浏览器中运行之前,它需要一个“转换器”。
TypeScript新手教程:为什么是TypeScript?
一开始,你不会完全理解TypeScript为什么有意义,毕竟它在变成JavaScript代码之前已经被剥离了。你会问:“TypeScript有什么用?”这是个好问题,我的朋友。
实际上,只要它能捕获代码中严重和愚蠢的错误,您就会看到它的好处。更重要的是,您的代码库将变得结构良好,并且几乎是自文档化的。您还将欣赏编辑器中改进的自动完成功能,但这只是一个不错的副作用。
不管怎么说,Twitter或“orange网站”上时不时会弹出一个新帖子,说TypeScript没用(TypeScript税)或太尴尬。
街垒的两边几乎都有游击队员。TypeScript有褒有贬,但重要的是,TypeScript是一个可靠的工具,把它放在你的工具带上不会有什么坏处。
我的目标是展示这个工具,并帮助你形成自己对TypeScript的看法。
初学者的TypeScript教程:设置TypeScript
设置?为什么如此?TypeScript不只是一种语言吗?种。TypeScript还有一个二进制代码,可以把TypeScript代码编译成JavaScript代码。记住,浏览器并不理解TypeScript。那么,让我们安装二进制文件。在一个新的文件夹中创建一个新的节点项目:
mkdir typescript-tutorial && cd $_
npm init -y
然后用以下方式安装TypeScript:
npm i typescript --save-dev
接下来,配置一个节点脚本,这样我们就可以轻松地运行TypeScript编译器了:
"scripts": {
"tsc": "tsc"
},
tsc代表TypeScript编译器,当编译器运行时,它会寻找一个名为tsconfig的文件。json在项目文件夹中。让我们为TypeScript生成一个配置文件:
npm run tsc -- --init
如果一切顺利,您将得到“消息TS6071:成功创建tsconfig。您将在项目文件夹中看到新文件。现在,保持冷静。tsconfig。json是一个可怕的配置文件。你不需要知道它的每一个要点。在下一节中,您将看到入门的相关部分。
TypeScript新手教程:配置TypeScript编译器
初始化一个git repo并提交原始tsconfig是一个好主意。在接触文件之前。我们将只保留一些配置选项,并删除其他所有选项。稍后,你可能会想要将你的版本与原始版本进行比较。为了开始打开tsconfig.json和替换所有的原始内容与以下:
{
"compilerOptions": {
"target": "es5",
"strict": true
}
}
保存并关闭该文件。首先,你可能想知道tsconfig是什么。json。TypeScript编译器和任何支持TypeScript的代码编辑器都会读取这个配置文件。
TypeScript编译成“普通的”JavaScript。关键目标确定所需的JavaScript版本ES5(或最新版本)。
这取决于tsconfig的“严格程度”。如果您没有将适当的类型注释添加到代码中,编译器和编辑器将遵守此规则(稍后将详细介绍这一点)。
当strict设置为true时,TypeScript会在你的代码中强制执行最大级别的类型检查:
noImplicitAny true:当变量没有定义类型时,TypeScript会报错
always sstrict true:严格模式是JavaScript的一种安全机制,它可以防止意外全局变量,默认此绑定,等等。当always sstrict设置为true时,TypeScript会在每个JavaScript文件的最顶部发出"use strict"。
还有更多可用的配置选项。随着时间的推移,你会学到更多,目前以上两个选项是你开始需要知道的一切。但"any"是什么意思?
关于types的几个单词
现在你应该知道TypeScript是做什么的了。一切都围绕着类型展开。它们不是典型的JavaScript“类型”,如String、Object、Boolean。TypeScript会自己添加更多类型,就像any(或更多)一样。
any是一个“松散的”TypeScript类型。这意味着:这个变量可以是任何类型:字符串,布尔值,对象,真的,我不在乎。这实际上就像根本没有类型检查一样。当strict设置为true时,你就会对TypeScript说“不要在我的代码中产生歧义”。
出于这个原因,我建议对TypeScript保持最大程度的严格,即使在一开始修复所有错误会比较困难。现在我们几乎已经准备好看到TypeScript的运行了!
初学者的TypeScript教程:TypeScript的作用
一切都以合法的(显然)JavaScript函数filterByTerm开头。在你的项目文件夹中创建一个名为filterByTerm.js的新文件,并将以下代码复制到其中:
function filterByTerm(input, searchTerm) {
if (!searchTerm) throw Error("searchTerm cannot be empty");
if (!input.length) throw Error("inputArr cannot be empty");
const regex = new RegExp(searchTerm, "i");
return input.filter(function(arrayElement) {
return arrayElement.url.match(regex);
});
}
filterByTerm("input string", "java");
如果你现在不明白其中的逻辑,也不要担心。在几行之后,我们来看看这个函数的参数以及它们是如何使用的。仅通过查看代码,您就应该已经发现了问题(不,它不是Java)。
我想知道是否有一种方法可以在我的IDE中检查这个函数,而不需要运行代码或使用Jest测试它。这可能吗?TypeScript在这方面做得很好,事实上,它是JavaScript中静态检查的最佳工具之一,也就是说,在你的代码运行之前“测试”它的正确性。
所以,进入TypeScript世界,把文件的扩展名从filterByTerm.js改为filterByTerm.ts。有了这个改变,你将发现一堆错误在你的代码:
你能看到函数参数下面的红色标记吗?从现在开始,我将以文本形式向你展示错误,但请记住,ide和文本编辑器会在你在TypeScript中出现错误时显示这些红线。
为了确认我们做错了什么,运行:
npm run tsc
看看这些错误:
ilterByTerm.ts:1:23 - error TS7006: Parameter 'input' implicitly has an 'any' type.
filterByTerm.ts:1:30 - error TS7006: Parameter 'searchTerm' implicitly has an 'any' type.
filterByTerm.ts:5:32 - error TS7006: Parameter 'arrayElement' implicitly has an 'any' type.
TypeScript是在告诉你函数参数有any类型,如果你记得的话,它可以是TypeScript中的任何类型。我们需要在TypeScript代码中添加适当的类型注释。
等等,到底什么是型?
什么是类型,JavaScript有什么问题?
JavaScript有类型,如果你在知道有字符串、布尔值、数字、对象等等之前使用过这种语言。到今天为止,JavaScript有8种类型:
String Number BigInt Boolean Null Undefined Object Symbol
列表中的所有内容都是“原语”,除了Object是类型。每个JavaScript类型都有一个对应的表示,可以在我们的代码中使用,比如字符串和数字:
var name = "Hello John";
var age = 33;
JavaScript的“问题”是,变量可以在它(或我们)想要的任何时候改变它的类型。例如,一个布尔值可以在以后变成字符串(将以下代码保存在名为types.js的文件中):
var aBoolean = false;
console.log(typeof aBoolean); // "boolean"
aBoolean = "Tom";
console.log(typeof aBoolean); // "string"
转换可以是有意的,开发人员可能真的想将“Tom”分配给aBoolean,但是这类错误很有可能是偶然发生的。
现在,从技术上讲,JavaScript本身并没有什么问题,因为它的“类型动态性”是有意为之的。JavaScript是作为一种简单的web脚本语言而诞生的,而不是作为一种成熟的企业语言。
然而,JavaScript放松自然会在代码中造成严重的问题,并破坏其可维护性。TypeScript旨在通过在JavaScript中添加强类型来解决这些问题。事实上,如果你把types.js的扩展改为types。你会在IDE中看到TypeScript在抱怨。
编译 types.ts 会产生:
types.ts:4:1 - error TS2322: Type '"Tom"' is not assignable to type 'boolean'.
涉足TypeScript类型
TypeScript围绕着类型展开,而我们的代码看起来根本没有类型。是时候加一些了。我们首先要确定函数参数。通过查看函数的调用方式,可以看出它有两个字符串作为参数:
filterByTerm("input string", "java");
我们确定吗?让我们向函数添加第一个类型注释。方法如下:
function filterByTerm(input: string, searchTerm: string) {
// omitted
}
// omitted
就是这样!通过给参数添加类型,我们将代码从纯JavaScript迁移到TypeScript。但如果你试图编译代码:
npm run tsc
发生了什么:
filterByTerm.ts:5:16 - error TS2339: Property 'filter' does not exist on type 'string'.
你能看到TypeScript是如何引导你的吗?问题在于过滤器函数:
function filterByTerm(input: string, searchTerm: string) {
// omitted
return input.filter(function(arrayElement) {
return arrayElement.url.match(regex);
});
}
我们告诉TypeScript“input”是一个字符串,但在后面的代码中,我们对它调用了filter方法,它属于数组。我们真正想要的是将"input"标记为一个数组,也许是一个字符串数组?
为此,您有两种选择。选项1带有string[]:
function filterByTerm(input: string[], searchTerm: string) {
// omitted
}
或者,如果你喜欢这个语法,选项2 Array<string>
:
function filterByTerm(input: Array<string>, searchTerm: string) {
// omitted
}
我个人更喜欢第二种选择。现在让我们尝试再次编译(npm运行tsc),下面是:
filterByTerm.ts:10:14 - error TS2345: Argument of type '"input string"' is not assignable to parameter of type 'string[]'.
filterByTerm("input string", "java");
我们将input标记为一个字符串数组,现在我们试图传入一个字符串。这很容易解决!让我们来传递一个字符串数组:
filterByTerm(["string1", "string2", "string3"], "java");
下面是到目前为止的完整代码:
function filterByTerm(input: Array<string>, searchTerm: string) {
if (!searchTerm) throw Error("searchTerm cannot be empty");
if (!input.length) throw Error("input cannot be empty");
const regex = new RegExp(searchTerm, "i");
return input.filter(function(arrayElement) {
return arrayElement.url.match(regex);
});
}
filterByTerm(["string1", "string2", "string3"], "java");
我觉得不错。但如果你编译它就不是(npm运行tsc):
filterByTerm.ts:6:25 - error TS2339: Property 'url' does not exist on type 'string'.
好的,TypeScript,很好。我们传入一个字符串数组,但在稍后的代码中,我们尝试访问一个名为“url”的属性:
return arrayElement.url.match(regex);
初学者TypeScript教程:TypeScript对象和接口
因为filterByTerm被传递给了一个字符串数组,所以TypeScript就开始抱怨了。"url"属性不存在类型字符串TypeScript。让我们通过传递一个对象数组来帮助TypeScript,其中每个对象都有需要的url属性:
filterByTerm(
[{ url: "string1" }, { url: "string2" }, { url: "string3" }],
"java"
);
当你在那里的时候,更新函数签名,让它接受一个对象数组:
function filterByTerm(input: Array<object>, searchTerm: string) {
// omitted
}
现在让我们编译代码:
npm run tsc
发生
filterByTerm.ts:6:25 - error TS2339: Property 'url' does not exist on type 'object'.
又来了!这是有意义的,至少在TypeScript中是这样:一般的JavaScript对象没有任何名为“url”的属性。对我来说,这是TypeScript真正开始发光的地方。
关键是,你不能给一个随机对象分配属性,然后就完事了。TypeScript要求代码中的每个实体都符合特定的形状。这个形状在TypeScript中有一个名字:interface。
现在,一开始它看起来像陌生的语法,但一旦你习惯了接口,你就会开始在所有地方使用它们。但是什么是界面呢?TypeScript中的接口就像一个合同。换句话说,接口就像实体的“模型”。
看看我们的代码,我们可以想到一个简单的“模型”,命名为Link,对象的形状应该符合以下模式:
它必须有一个类型为string的url属性
在TypeScript中,你可以用一个接口来定义这个“模型”,就像这样(把下面的代码放在filterByTerm.ts的顶部:
interface Link {
url: string;
}
在接口声明中,我们说:“从现在开始,我想在我的TypeScript代码中使用这个形状。”当然,这不是有效的JavaScript语法,它将在编译过程中被删除。
现在,我们可以通过修改参数"input"来使用我们的接口,它实际上也是一个自定义的TypeScript类型:
function filterByTerm(input: Array<Link>, searchTerm: string) {
// omitted
}
在这个修复中,我们对TypeScript说“期待一个Link数组”作为该函数的输入。下面是完整的代码:
interface Link {
url: string;
}
function filterByTerm(input: Array<Link>, searchTerm: string) {
if (!searchTerm) throw Error("searchTerm cannot be empty");
if (!input.length) throw Error("input cannot be empty");
const regex = new RegExp(searchTerm, "i");
return input.filter(function(arrayElement) {
return arrayElement.url.match(regex);
});
}
filterByTerm(
[{ url: "string1" }, { url: "string2" }, { url: "string3" }],
"java"
);
现在所有的错误都应该消失了,你可以运行:
npm run tsc
编译步骤将在项目文件夹中生成一个名为filterByTerm.js的文件,其中包含纯JavaScript代码。你可以签出这个文件,看看TypeScript的特定声明是如何被去掉的。
因为"always sstrict "被设置为true, TypeScript编译器也会在filterByTerm.js的顶部发出"use strict"。
你的第一个TypeScript代码做得很好!在下一节中,我们将进一步探讨接口。
TypeScript新手教程:接口和字段
TypeScript接口是该语言最强大的结构之一。接口有助于在应用程序中形成“模型”,以便任何开发人员在编写代码时都可以选择该模型并遵循它。
到目前为止,我们定义了一个简单的接口Link:
interface Link {
url: string;
}
如果你想在接口中添加更多的字段,你需要在block中声明它们:
interface Link {
description: string;
id: number;
url: string;
}
现在任何Link类型的对象都必须“实现”新字段,否则就会出现错误。实际上,通过编译代码:
npm run tsc
TypeScript对你吼叫:
filterByTerm.ts:17:4 - error TS2739: Type '{ url: string; }' is missing the following properties from type 'Link': description, id
问题是函数的参数:
filterByTerm(
[{ url: "string1" }, { url: "string2" }, { url: "string3" }],
"java"
);
TypeScript可以通过查看函数声明来推断参数的类型是Array of Link。因此,该数组中的任何对象必须具有(实现)接口链接中定义的所有字段。
大多数情况下,这还远远不够理想。毕竟,我们不知道每个Link类型的新对象是否都会有所有的字段。不用担心,要让编译通过,我们可以用一个问号声明接口的字段是可选的:
interface Link {
description?: string;
id?: number;
url: string;
}
TypeScript新手教程:变量类型
到目前为止,你已经看到了如何向函数的形参添加类型:
function filterByTerm(input: Array<Link>, searchTerm: string) {
//
}
TypeScript并不局限于此,当然,你也可以给任何变量添加类型。为了说明这个概念,让我们逐个提取函数的参数。首先,我要提取每个对象:
const obj1: Link = { url: "string1" };
const obj2: Link = { url: "string2" };
const obj3: Link = { url: "string3" };
注意我是如何告诉TypeScript obj1, obj2和obj3的类型是Link的。在“香草”JavaScript你会写:
const obj1 = { url: "string1" };
const obj2 = { url: "string2" };
const obj3 = { url: "string3" };
接下来,我们可以像这样定义一个Link数组:
const arrOfLinks: Array<Link> = [obj1, obj2, obj3];
最后是搜索词:
const term: string = "java";
最后完整的代码:
interface Link {
description?: string;
id?: number;
url: string;
}
function filterByTerm(input: Array<Link>, searchTerm: string) {
if (!searchTerm) throw Error("searchTerm cannot be empty");
if (!input.length) throw Error("input cannot be empty");
const regex = new RegExp(searchTerm, "i");
return input.filter(function(arrayElement) {
return arrayElement.url.match(regex);
});
}
const obj1: Link = { url: "string1" };
const obj2: Link = { url: "string2" };
const obj3: Link = { url: "string3" };
const arrOfLinks: Array<Link> = [obj1, obj2, obj3];
const term: string = "java";
filterByTerm(arrOfLinks, term);
好的,我理解你。与JavaScript相比,TypeScript看起来更冗长,有时甚至是多余的。但是随着时间的推移,您会发现添加的类型越多,您的代码就越健壮。
通过添加类型注释,你越多地帮助TypeScript理解代码的意图,你以后就会越好。这样你的开发经验就会飞速增长。
例如,现在arrOfLinks与正确的类型(Link的数组)相关联,编辑器可以推断数组中的每个对象都有一个名为url的属性,就像Link接口中定义的那样:
现在告诉我这不是很棒,因为它确实很棒。除了字符串、数组和数字,TypeScript还有很多其他类型。
有布尔值,元组,"any", never,枚举。假以时日,你会全都学会的。如果您好奇,请查看基本类型的文档。
现在让我们继续扩展接口。
(大多数时候,Typescript可以自己推断类型。作为经验法则,让它为你发挥作用吧!)
TypeScript新手教程:扩展接口
TypeScript接口很棒。然而,总有一天你需要在你的代码中添加一个新的实体,而这个实体恰好与另一个现有的接口几乎相同。例如,我们想要一个名为TranslatedLink的新接口,具有以下属性:
id, number url, string description, string language, string
描述、id和url…看起来我们已经有了具有相同属性的Link接口:
interface Link {
description?: string;
id?: number;
url: string;
}
有办法重用接口链接吗?原来,在TypeScript中,我们可以通过将接口的属性赋值给新接口来扩展接口,比如TranslatedLink就从Link“继承”了一些特性。下面是如何做到这一点,注意关键字extends:
interface Link {
description?: string;
id?: number;
url: string;
}
interface TranslatedLink extends Link {
language: string;
}
现在,任何TranslatedLink类型的对象都将具有可选属性description、id、url和新属性language:
interface Link {
description?: string;
id?: number;
url: string;
}
interface TranslatedLink extends Link {
language: string;
}
const link1: TranslatedLink = {
description:
"TypeScript tutorial for beginners is a tutorial for all the JavaScript developers ...",
id: 1,
url: "www.valentinog.com/typescript/",
language: "en"
};
当link1这样的对象使用接口时,我们说link1实现了该接口中定义的属性。另一方面,当接口用于描述代码中的一个或多个对象时,它就具有了实现。
扩展接口意味着借用它的属性并扩展它们以实现代码重用。但是等等,还有更多!你很快就会看到TypeScript接口也可以描述函数。
但首先让我们看看索引!
TypeScript新手教程:索引插曲
JavaScript对象是键/值对的容器。想象一个简单的物体:
const paolo = {
name: "Paolo",
city: "Siena",
age: 44
};
我们可以使用点语法访问任意键的值:
console.log(paolo.city);
或者使用括号语法(JavaScript数组也是如此,因为数组是一种特殊的对象):
console.log(paolo["city"]);
现在,假设键变成了动态的,这样我们就可以把它放到一个变量中,并在括号内引用它:
const paolo = {
name: "Paolo",
city: "Siena",
age: 44
};
const key = "city";
console.log(paolo[key]);
现在,让我们添加另一个对象,将两个对象都放到数组中,并像在filterByTerm.js中那样,使用filter方法过滤数组。但这一次键是动态传递的,因此可以通过任何键进行过滤:
const paolo = {
name: "Paolo",
city: "Siena",
age: 44
};
const tom = {
name: "Tom",
city: "Munich",
age: 33
};
function filterPerson(arr, term, key) {
return arr.filter(function(person) {
return person[key].match(term);
});
}
filterPerson([paolo, tom], "Siena", "city");
相关内容如下:
return person[key].match(term);
会工作吗?是的,因为JavaScript并不关心paolo或tom是否通过动态键“可索引”。那么TypeScript呢?在这种情况下它会给出一个错误吗?
让我们看看:在下一节中,我们将使用可变键使filterByTerm更加动态。
接口可以有索引
让我们回到filterByTerm。特别是filterByTerm函数:
function filterByTerm(input: Array<Link>, searchTerm: string) {
if (!searchTerm) throw Error("searchTerm cannot be empty");
if (!input.length) throw Error("input cannot be empty");
const regex = new RegExp(searchTerm, "i");
return input.filter(function(arrayElement) {
return arrayElement.url.match(regex);
});
}
它看起来不那么灵活,因为对于每个链接,我们都将硬编码的属性“url”与正则表达式匹配。我们可能想让属性,也就是键,是动态的。以下是第一个尝试:
function filterByTerm(
input: Array<Link>,
searchTerm: string,
lookupKey: string = "url"
) {
if (!searchTerm) throw Error("searchTerm cannot be empty");
if (!input.length) throw Error("input cannot be empty");
const regex = new RegExp(searchTerm, "i");
return input.filter(function(arrayElement) {
return arrayElement[lookupKey].match(regex);
});
}
lookupKey是动态键,它也会被分配一个默认参数作为回退,即字符串“url”。让我们编译代码:
npm run tsc
报错
error TS7053: Element implicitly has an 'any' type because expression of type 'string' can't be used to index type 'Link'.
No index signature with a parameter of type 'string' was found on type 'Link'.
这里是违规的一行:
return arrayElement[lookupKey].match(regex);
“没有索引签名”。哇。这是一个“容易”的修复。Head over the interface Link并添加索引:
interface Link {
description?: string;
id?: number;
url: string;
[index: string]: string;
}
语法有点奇怪,但类似于对象上的动态键访问。这意味着我们可以通过string类型的索引访问该对象的任何键,而该索引又返回另一个字符串。
不管怎样,第一次尝试会出现其他错误,比如:
error TS2411: Property 'description' of type 'string | undefined' is not assignable to string index type 'string'.
error TS2411: Property 'id' of type 'number | undefined' is not assignable to string index type 'string'.
这是因为接口上的一些属性是可选的,可能是未定义的,并且类型并不总是字符串(例如id是一个数字)。
我们可以尝试用联合类型来解决这个问题,这是一种TypeScript语法,用来定义两个或更多其他类型之间的联合类型:
interface Link {
description?: string;
id?: number;
url: string;
[index: string]: string | number | undefined;
}
以下行:
[index: string]: string | number | undefined;
表示index是一个字符串,可能返回另一个字符串、数字或未定义的值。尝试再次编译,这里有另一个错误:
error TS2339: Property 'match' does not exist on type 'string | number'.
return arrayElement[lookupKey].match(regex);
是有意义的。match方法只对字符串有效,并且我们的索引有可能返回一个数字。为了修复这个错误,我们可以使用anyas作为一个解决方案:
interface Link {
description?: string;
id?: number;
url: string;
[index: string]: any;
}
TypeScript编译器又高兴了,但代价是什么呢?
现在是时候把注意力转向TypeScript的另一个基本特性了:函数的返回类型。
TypeScript新手教程:函数的返回类型
到目前为止有很多新东西。总之,我跳过了TypeScript的另一个有用特性:函数的返回类型。
要理解为返回值添加类型注释为什么很方便,请想象一下我正在摆弄您的奇特函数。这是原始版本:
function filterByTerm(
input: Array<Link>,
searchTerm: string,
lookupKey: string = "url"
) {
if (!searchTerm) throw Error("searchTerm cannot be empty");
if (!input.length) throw Error("input cannot be empty");
const regex = new RegExp(searchTerm, "i");
return input.filter(function(arrayElement) {
return arrayElement[lookupKey].match(regex);
});
}
如果按原样调用,传入之前看到的链接数组和搜索词“string3”,它应该返回一个对象数组,如预期的那样:
filterByTerm(arrOfLinks, "string3");
// EXPECTED OUTPUT:
// [ { url: 'string3' } ]
但现在考虑一下另一种变体:
function filterByTerm(
input: Array<Link>,
searchTerm: string,
lookupKey: string = "url"
) {
if (!searchTerm) throw Error("searchTerm cannot be empty");
if (!input.length) throw Error("input cannot be empty");
const regex = new RegExp(searchTerm, "i");
return input
.filter(function(arrayElement) {
return arrayElement[lookupKey].match(regex);
})
.toString();
}
如果现在调用,使用相同的Link数组和搜索词“string3”,它将返回"[object Object]"!:
filterByTerm(arrOfLinks, "string3");
// WRONG OUTPUT:
// [object Object]
你能发现问题吗?提示:toString。
该函数没有按照预期工作,除非到达生产环境(或测试代码),否则您永远不会知道。幸运的是,TypeScript可以捕捉到这些错误,就像你在编辑器中写的那样。
这里是修复(只是相关的部分):
function filterByTerm(/* omitted for brevity */): Array<Link> {
/* omitted for brevity */
}
它是如何工作的?通过在函数体前添加类型注释,我们告诉TypeScript可以期待另一个数组作为返回值。现在这个漏洞很容易被发现。以下是目前为止的代码(修改版本):
interface Link {
description?: string;
id?: number;
url: string;
[index: string]: any;
}
function filterByTerm(
input: Array<Link>,
searchTerm: string,
lookupKey: string = "url"
): Array<Link> {
if (!searchTerm) throw Error("searchTerm cannot be empty");
if (!input.length) throw Error("input cannot be empty");
const regex = new RegExp(searchTerm, "i");
return input
.filter(function(arrayElement) {
return arrayElement[lookupKey].match(regex);
})
.toString();
}
const obj1: Link = { url: "string1" };
const obj2: Link = { url: "string2" };
const obj3: Link = { url: "string3" };
const arrOfLinks: Array<Link> = [obj1, obj2, obj3];
filterByTerm(arrOfLinks, "string3");
编译代码
npm run tsc
并检查错误:
error TS2322: Type 'string' is not assignable to type 'Link[]'.
太棒了。我们期待的是链接的数组,而不是字符串。要修复错误,请从过滤器末尾删除. tostring(),并再次编译代码。现在应该可以了!
我们向代码添加了另一层保护。当然,这个bug可以通过单元测试发现。TypeScript是一个很好的安全层,而不是测试的完全替代。
让我们继续探索类型别名!
TypeScript新手教程:类型别名vs接口
到目前为止,我们已经看到了接口作为描述对象和自定义类型的工具。但在其他人的代码中,您可能也会注意到关键字类型。
显然,interface和type在TypeScript中可以互换使用,但它们在很多方面是不同的。这让TypeScript初学者感到困惑。
记住:TypeScript中的接口是某种东西的形状,大多数时候是一个复杂对象。
另一方面,类型也可以用来描述自定义形状,但它只是一个别名,或者换句话说,是自定义类型的标签。例如,让我们想象一个有几个字段的接口,其中一个是boolean、number和string的联合类型:
interface Example {
authenticated: boolean | number | string;
name: string;
}
例如,通过类型别名,您可以提取自定义的联合类型,并创建名为Authenticated的标签:
type Authenticated = boolean | number | string;
interface Example {
authenticated: Authenticated;
name: string;
}
通过这种方式,您可以隔离更改,这样就不必在整个代码库中复制/粘贴union类型。
如果您想将type应用于我们的示例(filterByTerm),请创建一个名为Links的新标签,并将Array分配给它。这样你就可以引用类型:
// the new label
type Links = Array<Link>;
// the new label
function filterByTerm(
input: Links,
searchTerm: string,
lookupKey: string = "url"
): Links {
if (!searchTerm) throw Error("searchTerm cannot be empty");
if (!input.length) throw Error("input cannot be empty");
const regex = new RegExp(searchTerm, "i");
return input.filter(function(arrayElement) {
return arrayElement[lookupKey].match(regex);
});
}
const obj1: Link = { url: "string1" };
const obj2: Link = { url: "string2" };
const obj3: Link = { url: "string3" };
const arrOfLinks: Links = [obj1, obj2, obj3];
filterByTerm(arrOfLinks, "string3");
现在,这不是标签类型最聪明的例子但你应该明白要点。那么在接口和类型之间应该使用什么呢?我更喜欢复杂对象的接口。TypeScript文档也建议了一种方法:
因为软件的理想属性是对扩展开放的,所以如果可能的话,应该始终在类型别名上使用接口。
希望这有助于澄清你的疑惑。
在下一节中,在告别之前,我们将快速浏览另外两个TypeScript主题。继续前进!
TypeScript初学者教程:更多关于接口和对象的内容
函数是JavaScript的第一类公民,而对象是语言中最重要的实体。
对象大多是键/值对的容器,它们也可以容纳函数也就不足为奇了。当函数驻留在对象内部时,它可以通过关键字this访问“host”对象:
const tom = {
name: "Tom",
city: "Munich",
age: 33,
printDetails: function() {
console.log(`${this.name} - ${this.city}`);
}
};
到目前为止,你已经看到TypeScript接口应用于描述字符串和数字的简单对象。但他们能做的还有很多。让我们举个例子。创建一个名为interfaces-functions的新文件。ts,代码如下:
const tom = {
name: "Tom",
city: "Munich",
age: 33,
printDetails: function() {
console.log(`${this.name} - ${this.city}`);
}
};
它是一个JavaScript对象,但它需要类型。让我们做一个接口,使tom符合一个定义良好的形状。“IPerson”怎么样?同时,让我们也把新的接口应用到tom:
interface IPerson {
name: string;
city: string;
age: number;
}
const tom: IPerson = {
name: "Tom",
city: "Munich",
age: 33,
printDetails: function() {
console.log(`${this.name} - ${this.city}`);
}
};
编译报错
interfaces-functions.ts:11:3 - error TS2322: Type '{ name: string; city: string; age: number; printDetails: () => void; }' is not assignable to type 'IPerson'.
Object literal may only specify known properties, and 'printDetails' does not exist in type 'IPerson'.
很酷,IPerson没有任何名为printDetails的属性,但更重要的是它应该是一个函数。幸运的是,TypeScript接口也可以描述函数。方法如下:
interface IPerson {
name: string;
city: string;
age: number;
printDetails(): void;
}
这里我们添加了一个类型函数的属性printDetails,返回void。void作为函数的返回值很有用…不要返回任何东西。
输出到控制台的函数实际上不返回任何东西。如果要从printDetails返回一个字符串,可以将返回类型调整为string:
interface IPerson {
name: string;
city: string;
age: number;
printDetails(): string;
}
const tom: IPerson = {
name: "Tom",
city: "Munich",
age: 33,
printDetails: function() {
return `${this.name} - ${this.city}`;
}
};
现在,如果函数有参数呢?在接口中,你可以为它们添加类型注释:
interface IPerson {
name: string;
city: string;
age: number;
printDetails(): string;
anotherFunc(a: number, b: number): number;
}
如果你开始在实现IPerson的对象中输入“an…”,大多数IDE会自动为你完成这个功能。最好的开发人员生产力。