全网最全的,最详细的,最友好的 typescript 新手教程

500 阅读4分钟

欢迎关注我的公众号《人生代码》

前端入门到进阶图文教程,超详细的Web前端学习笔记。从零开始学前端,做一名精致优雅的前端工程师。公众号「人生代码」作者。

一个专注于分享美女图的网站,一个有趣的网站,一个具有后端管理系统,小程序一整套的网站,搜索美女图片功能 确认用户是否是选择男还是女的 图片预览功能 详情 用户点赞,评论,收藏 可以订阅别人的相册 可以创建相册 首页,用户中心,创建相册

欢迎点赞,关注,评论

本文翻译自

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会自动为你完成这个功能。最好的开发人员生产力。