>

项目中使用

- 编辑:www.bifa688.com -

项目中使用

时间: 2019-07-08阅读: 341标签: TypeScript

背景认识:

时间: 2019-11-03阅读: 156标签: TypeScript什么是 TypeScript

上周发布了一款名为Smartour的工具,是完全采用 TypeScript (以下简称 ts)来开发的。抛开以前做业务的时候的不完全使用,这次实践可以算是我第一次真正意义上的使用 ts。由于写法上的不同,以及对不熟悉事物的新鲜感,在这次项目开发的过程中着实有着许多感悟,于是打算写篇小东西好好记录下来。

TypeScript 是微软开发一款开源的编程语言,本质上是向 JavaScript 增加静态类型系统。它是 JavaScript 的超集,所有现有的 JavaScript 都可以不加改变就在其中使用。它是为大型软件开发而设计的,它最终编译产生 JavaScript,所以可以运行在浏览器、Node.js 等等的运行时环境。

官方网站的定义是:TypeScript 是 JS 类型的超集。它假设咱们知道什么是超集,什么是类型化。为了简单起见,你可以将TypeScript 看作是 JavaScript 之上的一个外壳

TS 能让人养成“先思考后动手”的好习惯

静态类型系统是什么

TypeScript是一个外壳,因为编写 TypeScript 的代码,在编译之后,,剩下的只是简单的 JS 代码。

在以往的开发过程中,我的习惯总是“先想好一个大概,然后边做边想再边改”。这样的好处是动作比较快,顺利的时候效率会很高,但更多的时候是不断地推翻自己先前的想法,相信不少的人也有跟我类似的体会。而使用 ts,可以在一定程度上减少这个问题。众所周知 ts 是强类型的语言,这也意味着它能有效制约开发者在开发过程中“随心所欲”的程度。就以定义一个函数的参数为例,可以看看我在写 js 和 ts 的思考方式上有什么不同。

增加静态这个定语,是为了和运行时的类型检查机制加以区分,强调静态类型系统是在编译时进行类型分析。

但是 JS 引擎无法读取 TypeScript 代码,因此任何 TypeScript 文件都应该经过预翻译过程,即编译。只有在第一个编译步骤之后,才剩下纯 JS 代码,可以在浏览器中运行。稍后会介绍 TypeScript 编译是如何完成的。

写 js 的时候,我的思考过程是这样的。

JavaScript 不是一个静态编译语言,不存在编译这一步骤。但从程序推理工具的角度来看,JavaScript 的配套中还是有不少的,比如ESLint这个不完备的程序推理工具

现在让我们记住 TypeScript 是一种特殊的 JS,在浏览器中运行之前它需要一个翻译。

首先这个参数是一个对象,这个对象的属性el是一个 css 选择器;而对象的属性keyNodes是一个数组,里面的元素是一系列的keyNode。这个所谓的keyNode也是一个对象,它也包含了一个 css 选择器属性el,以及一个绑定在 dom 元素上的事件参数event。我会把这个参数对象以注释的形式写下来,以便记住它的具体定义。

静态类型系统与 Lint 工具的关系

为什么要使用 TypeScript

/**{ el: '#demo-id', keyNodes: [{ el: '.item-1', event (e) { console.log(e) } }]} */

ESLint的定义:

刚开始,咱们不完全理解 TypeScript 为何有意义。 你可能会问“ TypeScript 的目的是什么”。 这是一个很好的问题。

以后任何地方要用到这个参数,我都要手动保证参数的结构要和这个注释保持一致,而换成 ts 的写法以后,我的思路是这样的:

Code linting is a type of static analysis that is frequently used to find problematic patterns or code that doesn’t adhere to certain style guidelines.

实际上,一旦它在您的代码中发现严重和愚蠢的错误,你就会看到 TypeScript 的好处。更重要的是,TypeScript 会让代码变得结构良好而且还是自动,这些还只是 TypeScript 的一小部分。

首先这个参数是一个对象,这个对象的属性el是一个 css 选择器;而对象的属性keyNodes是一个数组,里面的元素是一系列的keyNode。然后我会通过interface把它给定义好:

区别一

不管怎样,也经常有人说 TypeScript 没用,太过笨拙。

interface HightlightElement { el: string, keyNodes: ArrayKeyNode}interface KeyNode { el: string, event: EventListener}//在需要用到这个参数的时候,只需要在定义形参的时候传入这个 interface 即可。万一参数结构或内容的类型有误,VScode 编辑器都会立刻给予提示,省去了手动检查的麻烦。someFunction (param: HightlightElement) { ... }

同样强调Static Analysis,不过更强调Certain Style Guidelines,Lint 工具是一种团队协作时的风格规范工具。

凡事都有两面性,TypeScript 有很多反对者和支持者,但重要的是 TypeScript 是一个可靠的工具,将它放在咱们的工具技能包中不会造成伤害。

可以看到,在写 js 的时候更多的是“自己和自己约定,自己判断是否遵守了约定”,而 ts 则是“自己和自己约定以后,由第三方(编辑器)去判断是否遵守了约定”。这样的好处是除了老生常谈的减少错误之外,更多的则是对思维上的良性约束。这种良性约束能够让我们在思考的阶段就定义好接下来要做的一系列事情,在操作的过程中如果发现任何问题也能够在第一时间溯源回最初思考的起点,排查问题的时候会更加高效。

区别二

TypeScript 配置

TS 拥有自成文档的特性

静态类型类型分析和Lint 工具的区别在于Lint 工具没有Classifying phrases according to the kinds of values they compute。

为什么配置? TypeScript 还有一个二进制文件,可将 TypeScript 代码编译为 JS 代码. 请记住,浏览器不理解 TypeScript:

在写 js 的时候,我们依赖注释去判断某个变量或参数的类型、结构和作用。如果没有了注释,只能通过阅读源码和不断调试去搞清楚当中的细节。许多人在接手他人项目的时候都会有这么一个经历:“为什么不写注释!这个函数写的啥!这参数又是啥!”没有注释的 js 代码是让人崩溃的,但是写注释不仅需要时间,更考验一个人的概括能力。说了等于没说甚至误导性的注释,也是足够让人崩溃。

Lint 工具无法基于类型对程序进行静态分析,但两者都有基于CFG (控制流图,Control Flow Graph)对程序进行分析的能力。比如 TypeScript 的控制流分析、ESLint的complexity(当你想写个比较复杂的迭代算法时,这个规则就是个渣) 规则等。

mkdir typescript-tutorial  cd $_npm init -y

在 ts 中,除了注释以外我们还有另外一个选择,就是查看某个变量或参数所对应的interface接口定义。在interface中我们可以很直观地看到参数的结构,内部属性的类型,是否为可选等详细信息。再加上VScode 的智能提示及跳转,不管是查看他人的代码还是维护一个历史项目,都能更加方便和规范——毕竟写接口往往比写注释要顺手,看接口往往比猜代码要稳妥。

TypeScript 和 JavaScript 的关系

然后安装 TypeScript

说到自成文档的特性,我也联想到了另外一个热门技术GraphQL。借助GraphQL社区配套的一系列工具,调用方在调用接口的时候就能直接读到接口的标准定义;而接口的开发者也不需要额外编写文档,在定义接口的时候其实就相当于把文档也写好了。

和一些基于 JavaScript 的激进语言不同(比如 CoffeeScript),TypeScript 的语法设计首先考虑的就是兼容 JavaScript,或者说对 JavaScript 的语法做扩展。基本上是在 JavaScript 的基础之上增加了一些类型标记语法,以实现静态类型分析。把这些类型标注语法去掉之后,仍是一个标准的 JavaScript 语言。

npm i typescript --save-dev

自成文档的特性对于多人维护的项目来说是非常有用的,它能够大大降低项目当中沟通和理解的成本。但是这句话也有一个前提,就是开发者要遵守并合理工具当中的约束规范,如果一个接口的任何参数类型都是any,那么也就失去了使用 ts 的意义。

TypeScript 同样也在做一些新语法编译到老语法的事情(就像 Babel 做的), 基本实现常用的EcmaScript Stage 1以上的语法特性。

接下来在package.json中的scripts下添加如下内容,以便咱们可以轻松地运行 TypeScript 编译器:

TS 能够降低搭建环境的时间成本

类型系统的益处

 "scripts": { "tsc": "tsc" }

为了同时使用 js 新颖的特性以及兼容陈旧的浏览器,我们往往会借助一系列的工具去搭建一套开发环境。也许我们已经习惯了webpack babel的开发方式,可是又有谁能够保证自己在不看文档的情况下能够自己去搭一套呢?且不说这些工具各有着复杂的文档,就算好不容易把环境搭好了,还会发现有着更多“最佳实践”。改来改去花了一天时间,才终于算是完成。

侦测错误

tsc 代表 TypeScript 编译器,只要编译器运行,它将在项目文件夹中查找名为tsconfig.json的文件。 使用以下命令为 TypeScript 生成配置文件:

作为 js 的超集,我们可以在 ts 中放心使用 js 的各种高级能力。由于自带命令行工具,我们不再需要去研究babel或者各种preset-env插件,只需要指定需要构建的版本,ts 命令行工具就会自动为我们生成对应版本的 js。

静态类型分析首要优点就是能尽早的发现逻辑错误,而不是上线之后才发现。比如我们在 JavaScript 中经常发生的问题,函数返回值含混。在开发过程中坚信一个函数返回字符串,但到了线上接受了真实数据却返回了undefined。看似一个简单错误,却可能给公司造成数以万计的损失。

npm run tsc -- --init

当然这并不是说有了 ts 就能够完全抛弃构建工具了,在构建复杂应用(如包含各种静态资源,跨格式文件引用)场景的情况下还是离不开构建工具的,且在未来很长一段时间都会维持这种状况。但是秉承着“多一事不如少一事”的原则,只要能够减少哪怕是一个工具的使用,对开发者来说都是有好处的,毕竟我们都期待着某一个能够只管代码不管环境的日子。

看个例子。

执行成功后会在控制台收到message TS6071: Successfully created a tsconfig.json file。在项目文件夹中会看到新增了一个tsconfig.json文件。tsconfig。json是一个可怕的配置文件,不要慌。咱们不需要知道它的每一个要点,在下一节中,会介绍入门的相关部分。

尾声

// 通过分数获取图标

配置TypeScript 编译器

由于不是 ts 的资深玩家,以上的碎碎念都是作为一个初学者个人的新鲜感。在工作的这些日子里,也深刻体会到永远没有百分百理想化的东西。ts 固然是好,但也需要辩证地看待它。我们是否真的需要 ts?它是否真的能够提高我们的生产力?它是否真的如他人描述般理想?这些问题都需要经过实践才能回答。说到底 ts 只是一个工具,什么时候用它,怎么用它,还是取决于具体的场合。一味地尬吹或者否认其他的东西,只能说明思想还是太狭隘了。

functiongetRankIcon(score){

最好先初始化 git repo 并提交原始的tsconfig.json,然后再打开文件。 我们将只保留一些配置选项,并删除其他所有内容。 稍后,你可能需要将现在的版本与原始版本进行比较。

来自:

if(score >=100) {

首先,请打开tsconfig.json并将所有原始内容替换为以下内容:

return'';

{ "compilerOptions": { "target": "es5", "strict": true }}

}elseif(score >=500) {

保存并关闭文件。 首先,你可能想知道tsconfig.json是干什么的。 该配置文件由 TypeScript 编译器和任何具有 TypeScript 支持的代码编辑器读取。

return'';

noImplicitAnytrue:当变量没有定义类型时,TypeScript 会报错alwaysStricttrue:严格模式是 JS 的安全机制,它可以防止意外的全局变量,默认的this绑定等。 设置为 “alwaysStrict” 时,TypeScript 在每个KS 文件的顶部都使用“use strict”。

}elseif(score >=1500) {

有更多的配置选项可用。随着时间的推移,你会学到更多,因为现在上面的两个选择是你开始学习时需要知道的一切。

return'';

关于类型的几个词

}

TypeScript 支持与 JS 几乎相同的数据类型,此外,TypeScript 自己添加了更多的类型,如any类型一样。

}

“any”是松散的 TypeScript 类型。 这意味着:此变量可以是任何类型:字符串,布尔值,对象等。 实际上,这就像根本没有类型检查。

consticon = getRankIcon(5);

TypeScript 中的行为

consticonArray = icon.split();

咱们从一个合法的 KS函数开始:filterByTerm。在项目文件夹中创建一个名为filterByTerm.js的新文件,并输入以下内容

执行

function filterByTerm(input, searchTerm) { if (!searchTerm) throw Error("searchTerm 不能为空"); if (!input.length) throw Error("input 不能为空"); const regex = new RegExp(searchTerm, "i"); return input.filter(function(arrayElement) { return arrayElement.url.match(regex); });}filterByTerm("input string", "java");

> node taste.js

如果现在不了解逻辑,请不要担心。 看一下该函数的参数,以及几行之后如何使用它们。 只需查看代码,就应该已经发现了问题。

TypeError: Cannot read property 'split' of undefined

我想知道是否有一种方法可以在我的 IDE 中检查这个函数,而不需要运行代码或者用Jest测试它。这可能吗? TypeScript 在这方面做得很好,实际上它是 JS 中静态检查的最佳工具,也就是说,在代码运行之前测试代码的正确性

相同的逻辑我们用tsc编译一下(甚至不需要增加任何的类型标注)。直接静态分析出来程序有一个undefined。

因此,咱们改用 TypeScript ,将文件的扩展名从filterByTerm.js改为filterByTerm.ts。通过这种更改,你会发现代码中的一堆错误

> tsc --strictNullChecks taste.ts

可以看到函数参数下的有很多红色标记。从现在开始,会向你展示文本形式的错误,但是请记住,当咱们在TypeScript 中出错时,IDE 和文本编辑器都会显示这些红线。

x.ts(11,19): error TS2532: Object is possibly 'undefined'.

确定哪个地方错:

另一个重要的用处是作为维护工具(重构辅助工具),假如我们有一个很通用的函数,在工程里用的到处都是,有一天我们要在这个函数最前面增加一个参数。TypeScript 中你只需要改那个函数就好了,然后再执行静态类型分析,所有和这个函数参数不匹配的地方都会提示出来。但是,在 JavaScript 里,这个改动很有可能被忽略或者漏掉,打包也不会报错,然后发布后线上就挂了……

npm run tsc

抽象

可以看到控制的报错:

类型系统的另一个优点是强化规范编程,TypeScript 提供了简便的方式定义接口。这一点在大型软件开发时尤为重要,一个系统模块可以抽象的看做一个 TypeScript 定义的接口。

filterByTerm.ts:1:23 - error TS7006: Parameter 'input' implicitly has an 'any' type.1 function filterByTerm(input, searchTerm) { ~~~~~filterByTerm.ts:1:30 - error TS7006: Parameter 'searchTerm' implicitly has an 'any' type.1 function filterByTerm(input, searchTerm) { ~~~~~~~~~~filterByTerm.ts:5:32 - error TS7006: Parameter 'arrayElement' implicitly has an 'any' type.5 return input.filter(function(arrayElement) {

用带清晰接口的模块来结构化大型系统,这是一种更为抽象的设计形式。接口设计(讨论)与最终实现方式无关,对接口思考得越抽象越有利。

TypeScript 告诉你函数参数具有“any”类型,如果还记得的话,它可以是 TypeScript 中的any类型。 我们需要在我们的 TypeScript 代码中添加适当的类型注释。

换句话说就是让设计脱离实现,最终体现出一种IDL(接口定义语言,Interface Define Language),让程序设计回归本质。

什么是类型,JS 中有什么问题

看个例子。

到目前为止,JS 有七种类型

interface Avatar {

StringNumberBooleanNullUndefinedObjectSymbol (ES6)

cdnUrl: string;// 用户头像在 CDN 上的地址

除了 Object 类型外,其它是 JS 的基本数据类型。每种 JS 类型都有相应的表示,可以代码中使用,比如字符串和数字

filePath: string;// 用户头像在对象存储上的路径

var name = "Hello John";var age = 33;

fileSize: number;// 文件大小

JS 的问题是,变量可以随时更改其类型。例如,布尔值可以变成字符串(将以下代码保存到名为types.js的文件中)

}

var aBoolean = false;console.log(typeof aBoolean); // "boolean"aBoolean = "Tom";console.log(typeof aBoolean); // "string"

interface UserProfile {

转换可以是有意的,开发人员可能真的希望将Tom分配到aBoolean,但是这类错误很可能是偶然发生的。

cuid?: string;// 用户识别 ID,可选

从技术上讲,JS 本身没有什么问题,因为它的类型动态是故意的。JS 是作为一种简单的 web 脚本语言而诞生的,而不是作为一种成熟的企业语言。

avatar?: Avatar;// 用户形象,可选

然而,JS 松散的特性可能会在你的代码中造成严重的问题,破坏其可维护性。TypeScript 旨在通过向 JS 添加强类型来解决这些问题。实际上,如果将types.js的扩展更改为types.ts。你会在IDE中看到 TypeScript 的抱怨。

name: string;// 用户名,必选

types.ts的编译控制台会报错:

gender: string;// 用户性别,必选

types.ts:4:1 - error TS2322: Type '"Tom"' is not assignable to type 'boolean'.

age: number;// 用户年龄,必选

有了这些知识,接着,咱们更深入地研究 TypeScript 类型。

}

深入 TypeScript 类型

interface UserModel {

TypeScript 强调有类型,咱们上面的代码根本没有类型,是时候添加一些了。首先要修正函数参数。通过观察这个函数是如何调用的,它似乎以两个字符串作为参数:

createUser(profile: UserProfile): string;// 创建用户

filterByTerm("input string", "java");

getUser(cuid: string): UserProfile;// 根据 cuid 获取用户

为参数添加类型:

listFollowers(cuid: string): UserProfile[];// 获取所有关注者

function filterByTerm(input: string, searchTerm: string) { // ...}// ...

followByCuid(cuid: string, who: string): string;// 关注某人

接着编译:

}

npm run tsc

那我实现上述Interface也只需如下进行。

剩下的错误:

classUserModelImplimplementsUserModel{

filterByTerm.ts:5:16 - error TS2339: Property 'filter' does not exist on type 'string'.

createUser(profile: UserProfile): string {

可以看到 TypeScript 是如何指导我们,现在的问题在于filter方法。

// do something

function filterByTerm(input: string, searchTerm: string) { // 省略一些 return input.filter(function(arrayElement) { return arrayElement.url.match(regex); });}

}

咱们告诉 TypeScript“input”是一个字符串,但是在后面的代码中调用了filter方法,它属于数组。我们真正需要的是将输入标记为某个东西的数组,可能是字符串数组:

// 把 UserModel 定义的都实现

为此,有两个选择。选项1:string[]

}

function filterByTerm(input: string[], searchTerm: string) { // ...}

文档

选项2:ArrayType

读程序时类型标注也有用处,不止是说人在读的时候。基于类型定义 IDE 可以对我们进行很多辅助,比如找到一个函数所有的使用,编写代码时对参数进行提示等等。

function filterByTerm(input: Arraystring, searchTerm: string) { // ...}

更重要的是这种文档能力不像纯人工维护的注释一样,稍不留神就忘了更新注释,最后注释和程序不一致。

我个人更喜欢选项2。 现在,尝试再次编译(npm run tsc),控制台信息如下:

更强大的是,可以自动根据类型标注产生文档,甚至都不需要编写注释(详细的人类语言描述还是要写注释的)。

filterByTerm.ts:10:14 - error TS2345: Argument of type '"input string"' is not assignable to parameter of type 'string[]'.filterByTerm("input string", "java");

首先安装全局的typedoc命令。

TypeScript 还会校验传入的类型。 我们将input改为字符串数组:

> npm install -g typedoc

filterByTerm(["string1", "string2", "string3"], "java");

然后我们尝试对上面抽象的Interface产生文档。

这是到目前为止的完整代码:

> typedoc taste.ts --module commonjs --out doc

function filterByTerm(input: Arraystring, searchTerm: string) { if (!searchTerm) throw Error("searchTerm 不能为空"); if (!input.length) throw Error("input 不能为空"); const regex = new RegExp(searchTerm, "i"); return input.filter(function(arrayElement) { return arrayElement.url.match(regex); });}filterByTerm(["string1", "string2", "string3"], "java");

然后下面就是效果了。

看上去很好,但是,编译(npm run tsc)还是过不了:

图片 1

filterByTerm.ts:6:25 - error TS2339: Property 'url' does not exist on type 'string'.

编写第一个 TypeScript 程序

TypeScript 确实很严谨。 我们传入了一个字符串数组,但是在代码后面,尝试访问一个名为“url”的属性:

这一节会介绍如何开始体验 TypeScript,下一节开始会介绍一些有特点、有趣的例子。

return arrayElement.url.match(regex);

前置准备

这意味着咱们需要一个对象数组,而不是字符串数组。咱们在下一节中解决这个问题。

安装 TypeScript。

TypeScript 对象和接口

npm install -g typescript

上面遗留一个问题:因为filterByTerm被传递了一个字符串数组。url属性在类型为string的TypeScript 上不存在。所以咱们改用传递一个对象数组来解决这个问题:

初始化工作区。

filterByTerm( [{ url: "string1" }, { url: "string2" }, { url: "string3" }], "java");

mkdir learning-typescript

函数定义也要对应的更改:

cd learning-typescript

function filterByTerm(input: Arrayobject, searchTerm: string) { // omitted}

新建第一个测试文件。

现在让我们编译代码

touch taste.ts

npm run tsc

第一个例子

控制输出:

我们刚才已经新建了一个名为taste.ts的文件,对 TypeScript 的后缀名为ts,那我们写点什么进去吧!

filterByTerm.ts:6:25 - error TS2339: Property 'url' does not exist on type 'object'.

taste.ts

又来了,通用 JS 对象没有任何名为url的属性。对我来说,TypeScript 对类型要求真的是很严谨。

functionsay(text: string){

这里的问题是,咱们不能给一个随机对象分配属性,TypeScript 的核心原则之一是对值所具有的结构进行类型检查, 在 TypeScript 里,接口(interface)的作用就是为这些类型命名和为你的代码或第三方代码定义契约,咱们可以使用接口来解决这个问题。

console.log(text);

通过查看我们的代码,我们可以想到一个名为Link的简单"模型",其结构应该符合以下模式:它必须有一个类型为string的url属性。

}

在TypeScript 中,你可以用一个接口来定义这个模型,就像这样(把下面的代码放在filterByTerm.ts的顶部):

say('hello!');

interface ILink { url: string;}

然后执行命令(tsc 是刚才 npm 装的 typescript 中带的)。

对于接口声明,这当然不是有效的 JS 语法,在编译过程中会被删除。

tsc taste.ts

提示:在定义接口名字前面加上大写的I,这是 TypeScript 的惯例。

然后我们得到一个编译后的文件taste.js,内容如下。

现在,使用使用接口ILink定义input类型

functionsay(text){

function filterByTerm(input: ArrayILink, searchTerm: string) { // ...}

console.log(text);

通过此修复,可以说 TypeScript “期望ILink数组”作为该函数的输入,以下是完整的代码:

}

interface ILink { url: string;}function filterByTerm(input: ArrayILink, searchTerm: string) { if (!searchTerm) throw Error("searchTerm 不能为空"); if (!input.length) throw Error("input 不能为空"); const regex = new RegExp(searchTerm, "i"); return input.filter(function(arrayElement) { return arrayElement.url.match(regex); });}filterByTerm( [{ url: "string1" }, { url: "string2" }, { url: "string3" }], "java");

say('hello!');

此时,所有的错误都应该消失了,可以运行了:

可以看到,只是简单去除了 text 后面的类型标注,然后我们用node执行taste.js。

npm run tsc

node taste.js

编译后,会在项目文件夹中生成一个名为filterByTerm.js的文件,其中包含纯 JS 代码。可以检出该文件并查看 TypeScript 特定的声明最终转换成 JS 的是什么样的。

// hello!

因为alwaysStrict设置为true,所以 TypeScript 编译器也会在filterByTerm.js的顶部使用use strict。

完美执行,让我再改写东西看看?

接口和字段

taste.ts

TypeScript 接口是该语言最强大的结构之一。接口有助于在整个应用程序中形成模型,这样任何开发人员在编写代码时都可以选择这种模型并遵循它。

functionsay(text: string){

前面,咱们定义了一个简单的接口ILink

console.log(text);

interface ILink { url: string;}

}

如果您想要向接口添加更多的字段,只需在块中声明它们即可:

say(969);

interface ILink { description: string; id: number; url: string;}

然后再执行tsc taste.ts,然后就类型检查就报错了。这就是 TypeScript 的主要功能 —— 静态类型检查。

现在,类型为ILink的对象都必须实现新字段,否则就会出现错误,如果把上面 的定义重新写入filterByTerm.ts然后重新编译就会报错了:

> tsc taste.ts

filterByTerm.ts:17:4 - error TS2739: Type '{ url: string; }' is missing the following properties from type 'ILink': description, id

taste.ts(4,5): error TS2345: Argument of type '969' is not assignable to parameter of type 'string'.

问题在于我们函数的参数:

有趣的例子 - 基于控制流的分析

filterByTerm( [{ url: "string1" }, { url: "string2" }, { url: "string3" }], "java");

看一个 JavaScript 的例子。

TypeScript 可以通过函数声明来推断参数是ILink的类型数组。因此,该数组中的任何对象都必须实现接口ILink中定义的所有字段

functiongetDefaultValue(key, emphasis){

大多数情况下,实现所有字段是不太现实的。毕竟,咱也不知道ILink类型的每个新对象是否会需要拥有所有字段。不过不要担心,要使编译通过,可以声明接口的字段可选,使用?表示:

letret;

interface ILink { description?: string; id?: number; url: string;}

if(key ==='name') {

现在编辑器和编译器都没问题了。然而 TypeScript 接口可以做的更多,在下一节我们将看到如何扩展它们。但首先简要介绍一下 TypeScript 中的变量。

ret ='GuangWong';

变量声明

}elseif(key==='gender') {

到目前为止,咱们已经了解了如何向函数参数中添加类型:

ret ='Man';

function filterByTerm(input: ArrayILink, searchTerm: string) { //}

}elseif(key ==='age') {

TypeScript 并不限于此,当然也可以向任何变量添加类型。为了说明这个例子,咱们一一地提取函数的参数。首先咱要提取每一个单独的对象:

ret =23;

const obj1: ILink = { url: "string1" };const obj2: ILink = { url: "string2" };const obj3: ILink = { url: "string3" };

}else{

接下来我们可以像这样定义一个ILink数组:

thrownewError('Unkown key ' info.type);

const arrOfLinks: ArrayILink = [obj1, obj2, obj3];

}

参数searchTerm对应的类型可以这样:

if(emphasis) {

const term: string = "java";

ret = ret.toUpperCase();

以下是完整的代码:

}

interface ILink { description?: string; id?: number; url: string;}function filterByTerm(input: ArrayILink, searchTerm: string) { if (!searchTerm) throw Error("searchTerm 不能为空"); if (!input.length) throw Error("input 不能为空"); const regex = new RegExp(searchTerm, "i"); return input.filter(function(arrayElement) { return arrayElement.url.match(regex); });}const obj1: ILink = { url: "string1" };const obj2: ILink = { url: "string2" };const obj3: ILink = { url: "string3" };const arrOfLinks: ArrayILink = [obj1, obj2, obj3];const term: string = "java";filterByTerm(arrOfLinks, term);

returnret;

与 JS 相比,TypeScript看起来更冗长,有时甚至是多余的。但是随着时间的推移,会发现添加的类型越多,代码就越健壮。

}

通过添加类型注释,对 TypeScript 的了解也越多,还可以帮助你更好地理解代码的意图。

getDefaultValue('name');// GuangWong

例如,arrOfLinks与正确的类型(ILink的数组)相关联,咱们编辑器就可以推断出数组中的每个对象都有一个名为url的属性,如接口ILink中所定义:

getDefaultValue('gender',true)// MAN

除了字符串、数组和数字之外,TypeScript 还有更多类型。有boolean,tuple (元组)any,never,enum。如果你感兴趣,可以查看文档。

getDefaultValue('age',true)// Error: toUpperCase is not a function

现在,咱们继续扩展接口。

这是一个简单的函数,第一个参数key用来获得一个默认值。第二参数emphasis为了某些场景下要大写强调,只需要传入true即可自动将结果转成大写。

扩展接口

但是我不小心将age的值写成了数字字面量,如果我调用getDefaultValue('age', true)就会在运行时报错。这个有可能是软件上线了之后才发生,直接导致业务不可用。

TypeScript 接口很好。但是,如果哪天咱们需要一个新的对象,所需的类型跟现在有接口基本差不多。假设我们需要一个名为IPost的新接口,它具有以下属性:

TypeScript 就能避免这类问题,我们只需要进行一个简单的标注。

id, numbertitle, stringbody, stringurl, stringdescription, string

functiongetDefaultValue(key, emphasis?){

该接口的字段其中有些,我们ILink接口都有了。

letret: string;

interface ILink { description?: string; id?: number; url: string;}

if(key ==='name') {

是否有办法重用接口ILink? 在 TypeScript 中,可以使用继承来扩展接口,关键字用extends表示:

ret ='GuangWong';

interface ILink { description?: string; id?: number; url: string;}interface IPost extends ILink { title: string; body: string;}

}elseif(key ==='gender') {

现在,IPost类型的对象都将具有可选的属性description、id、url和必填的属性title和body:

ret ='Man';

interface ILink { description?: string; id?: number; url: string;}interface IPost extends ILink { title: string; body: string;}const post1: IPost = { description: "TypeScript tutorial for beginners is a tutorial for all the JavaScript developers ...", id: 1, url: "www.valentinog.com/typescript/", title: "TypeScript tutorial for beginners", body: "Some stuff here!"};

}elseif(key ==='age') {

当像post1这样的对象使用一个接口时,我们说post1实现了该接口中定义的属性。

ret =23;

扩展接口意味着借用其属性并扩展它们以实现代码重用。当然 TypeScript 接口还也可以描述函数,稍后会看到。

}else{

索引

thrownewError('Unkown key ' key);

JS 对象是键/值对的容器。 如下有一个简单的对象:

}

const paolo = { name: "Paolo", city: "Siena", age: 44};

if(emphasis) {

我们可以使用点语法访问任何键的值:

ret = ret.toUpperCase();

console.log(paolo.city);

}

现在假设键是动态的,我们可以把它放在一个变量中,然后在括号中引用它

returnret;

const paolo = { name: "Paolo", city: "Siena", age: 44};const key = "city";console.log(paolo[key]);

}

现在咱们添加另一个对象,将它们都放到一个数组中,并使用filter方法对数组进行筛选,就像我们在filterByTerm.js中所做的那样。但这一次是动态传递的,因此可以过滤任何对象

getDefaultValue('name');// GuangWong

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");

getDefaultValue('gender',true)// MAN

这是比较重要的一行行:

getDefaultValue('age',true)// Error: toUpperCase is not a function

return person[key].match(term);

在tsc编译时,逻辑错误会自动报出来。妈妈再也不怕我的逻辑混乱了!

能行吗 是的,因为 JS 不在乎paolo或tom是否可通过动态[key]进行“索引化”。 那在 TS 又是怎么样的呢?

> tsc taste.ts

在下一部分中,我们将使用动态键使filterByTerm更加灵活。

x.ts(8,5): error TS2322: Type '23' is not assignable to type 'string'.

接口可以有索引

有趣的例子 - Interface

让我们回到filterByTerm.tsfilterByTerm函数

JavaScript 的类型我们称为鸭子类型。

function filterByTerm(input: ArrayILink, searchTerm: string) { if (!searchTerm) throw Error("searchTerm 不能为空"); if (!input.length) throw Error("input 不能为空"); const regex = new RegExp(searchTerm, "i"); return input.filter(function(arrayElement) { return arrayElement.url.match(regex); });}

当看到一只鸟走起来像鸭子、游泳起来像鸭子、叫起来也像鸭子,那么这只鸟就可以被称为鸭子。

它看起来不那么灵活,因为对于每个ILink,咱们都使用硬编码方式将属性url与正则表达式相匹配。我们希望使动态属性(也就是键)让代码更灵活:

鸭子类型总是有点损的感觉,不如叫做面向接口编程。所以 JavaScript 就是一门面向接口编程的语言,TypeScript 中相对应的就是Interface。

function filterByTerm( input: ArrayILink, searchTerm: string, lookupKey: string = "url") { if (!searchTerm) throw Error("searchTerm 不能为空"); if (!input.length) throw Error("input 不能为空"); const regex = new RegExp(searchTerm, "i"); return input.filter(function(arrayElement) { return arrayElement[lookupKey].match(regex); });}

接下来看个例子。

lookupKey是动态键,这是给它分配了默认参数“url”。 接着编译代码:

interface Profile {

npm run tsc

name: string;

当然会报错:

gender:'man'|'woman';

error TS7053: Element implicitly has an 'any' type because expression of type 'string' can't be used to index type 'ILink'. No index signature with a parameter of type 'string' was found on type 'ILink'.

age: number;

出错行:

height?: number;

return arrayElement[lookupKey].match(regex);

}

元素隐式具有"any"类型,因为类型“ILink”没有索引签名,需要你添加一个索引到对象的接口,这很容易解决。

functionprintProfile(profile: Profile){

转到接口ILink并添加索引:

console.log('name', profile.name);

interface ILink { description?: string; id?: number; url: string; [index: string] : string}

console.log('gender', profile. gender);

语法有点奇怪,但类似于对象上的动态键访问。这意味着我们可以通过类型为string的索引访问该对象的任何键,该索引反过来又返回另一个字符串。

console.log('age', profile.age);

不过,这样写会引发其它错误:

if(profile.height) {

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'.

console.log('height', profile.height);

这是因为接口上的一些属性是可选的,可能是undefined,而且返回类型不总是string(例如,id 是一个number)。

}

interface ILink { description?: string; id?: number; url: string; [index: string]: string | number | undefined;}

}

这一行:

printProfile({name:'GuangWong', gender:'man', age:23});

[index: string]: string | number | undefined;

使用tsc编译一切完美,那我们尝试下面的调用。

表示该索引是一个字符串,可以返回另一个字符串、数字或undefined。尝试再次编译,这里有另一个错误

printProfile({name:'GuangWong', age:23});

error TS2339: Property 'match' does not exist on type 'string | number'.return arrayElement[lookupKey].match(regex);

使用tsc编译,报错了!说没有传属性gender。不过height也没传怎么没报错呢?因为height?: number,其中的?表示这个是可选的。

报的没毛病。match方法只存在字符串中 ,而且我们的索引有可能返回一个number。为了修正这个错误,我们可以使用any类型:

> tsc taste.ts

interface ILink { description?: string; id?: number; url: string; [index: string]: any;}

x.ts(19,14): error TS2345: Argument of type '{ name: string; age: number; }' is not assignable to parameter of type 'Profile'.

再次编译通过。

Property 'gender' is missing in type '{ name: string; age: number; }'.

函数的返回类型

接下来我们试着传个非number的height试试看。

到目前为止有很多新东西。现在来看看 TypeScript 的另一个有用的特性:函数的返回类型

printProfile({height:'190cm', name:'GuangWong', gender:'man', age:23});

回到filterByTerm函数:

使用tsc编译,报错了!string类型无法赋值给number类型。

function filterByTerm( input: ArrayILink, searchTerm: string, lookupKey: string = "url") { if (!searchTerm) throw Error("searchTerm 不能为空"); if (!input.length) throw Error("input 不能为空"); const regex = new RegExp(searchTerm, "i"); return input.filter(function(arrayElement) { return arrayElement[lookupKey].match(regex); });}

> tsc taste.ts

如果按原样调用,传递前面看到的ILink数组和搜索词string3,则如预期的那样返回一个对象数组:

x.ts(17,14): error TS2345: Argument of type '{ height: string; name: string; gender: "man"; age: number; }' is not assignable to parameter of type 'Profile'.

filterByTerm(arrOfLinks, "string3"); // EXPECTED OUTPUT:// [ { url: 'string3' } ]

Types of property 'height' are incompatible.

但现在考虑一个更改的变体:

Type 'string' is not assignable to type 'number'.

function filterByTerm( input: ArrayILink, 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();}

有趣的例子 - Implements

如果现在调用,使用相同的ILink数组和搜索词string3,它将返回[object object]

这也是Interface的应用,假设我们有这么一个Interface,是某个架构师写的让我来实现一种事物,比如榴莲。

filterByTerm(arrOfLinks, "string3");// WRONG OUTPUT:// [object Object]

type Fell ='good'|'bad';

该函数没有按照预期工作,如果对 JS 隐式类型转换不清楚就很难发现问题。幸运的是,TypeScript 可以捕获这些错误,就像你在编辑器中写的那样。

interface Eatable {

修正如下:

calorie: number;

function filterByTerm(/* 省略 */): ArrayILink { /* 省略 */}

looks(): Fell;

它是如何工作的? 通过在函数体之前添加类型注释,告诉 TypeScript 期望另一个数组作为返回值。现在这个bug 很容易被发现。

taste(): Fell;

interface ILink { description?: string; id?: number; url: string; [index: string]: any;}function filterByTerm( input: ArrayILink, searchTerm: string, lookupKey: string = "url"): ArrayILink { 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: ILink = { url: "string1" };const obj2: ILink = { url: "string2" };const obj3: ILink = { url: "string3" };const arrOfLinks: ArrayILink = [obj1, obj2, obj3];filterByTerm(arrOfLinks, "string3");

flavour(): Fell;

现在编译并检查错误:

}

error TS2322: Type 'string' is not assignable to type 'ILink[]'.

我只需要简单的实现Eatable即可,即implements Eatable。

咱们希望返回值的是ILink数组,而不是字符串。要修复此错误,从末尾删除.tostring()并重新编译代码就行了。

classDurianimplementsEatable{

类型别名 vs 接口

calorie =1000;

到目前为止,我们已经将接口视为描述对象和自定义类型的工具。但是通过其他人的代码,你可能也注意到了关键字的type

looks(): Fell {

显然,interface和 type 在 TypeScript 中可以互换使用,但是它们在许多方面有所不同,这就是TypeScript 给初学者的困惑。

return'good';

请记住: TypeScript 中的接口描述是某个东西的结构,大多数情况下是一个复杂的对象。

}

另一方面,type也可以用来描述自定义的结构,但它只是一个别名,或者换句话说,是自定义类型的标签。例如,设想一个有两个字段的接口,其中一个是布尔型、数字型和字符串型的联合类型

taste(): Fell {

interface IExample { authenticated: boolean | number | string; name: string;}

return'good';

例如,使用type 别名可以提取自定义联合类型,并创建名为Authenticated的标签

}

type Authenticated = boolean | number | string;interface IExample { authenticated: Authenticated; name: string;}

flavour(): Fell {

通过这种方式,咱可以隔离所做的更改,就不必在整个代码库中复制/粘贴联合类型

return'bad';

如果要将type应用上面示例(filterByTerm),创建一个名为ILinks的新标签,并将Array ILink分配给它。 这样,就可以引用前者:

}

// the new labeltype ILinks = ArrayILink;// the new labelfunction filterByTerm( input: ILinks, searchTerm: string, lookupKey: string = "url"): ILinks { if (!searchTerm) throw Error("searchTerm 不能为空"); if (!input.length) throw Error("input 不能为空"); const regex = new RegExp(searchTerm, "i"); return input.filter(function(arrayElement) { return arrayElement[lookupKey].match(regex); });}const obj1: ILink = { url: "string1" };const obj2: ILink = { url: "string2" };const obj3: ILink = { url: "string3" };const arrOfLinks: ILinks = [obj1, obj2, obj3];filterByTerm(arrOfLinks, "string3");

}

当然,这不是type用法最好事例。那么在interfacetype之间使用哪个呢? 我更喜欢复杂对象的接口。TypeScript 文档也建议了。

如果我删掉flavour的实现,那就会报错了!说我错误的实现了Eatable。

一个软件的理想状态是可以扩展,因此,如果可能,应始终在类型别名上使用接口。

> tsc taste.ts

更多关于接口和对象的知识点

x.ts(8,7): error TS2420: Class 'Durian' incorrectly implements interface 'Eatable'.

函数是 JS 中的一等公民,而对象是该语言中最重要的实体。

Property 'flavour' is missing in type 'Durian'.

对象大多是键/值对的容器,它们也可以保存函数,这一点也不奇怪。当一个函数位于一个对象内部时,它可以通过关键字this访问“宿主”对象:

有趣的例子 - 函数重载

const tom = { name: "web前端", city: "厦门", age: 26, printDetails: function() { console.log(`${this.name} - ${this.city}`); }};

什么重载啊、多态啊、分派啊,在 JavaScript 里都是不存在的!那都是都是我们 Hacking 出来,Ugly!

到目前为止,咱们已经看到 TypeScript 接口应用于简单对象,用于描述字符串和数字。 但是他们可以做的更多。 举个例, 使用以下代码创建一个名为interfaces-functions.ts的新文件:

TypeScript 对函数重载有一定的支持,不过因为 TypeScript 不扩展 JavaScript 的运行时机制,还是需要我们来处理根据宗量分派的问题(说白了就是运行时类型判断)。

const tom = { name: "web前端", city: "厦门", age: 26, printDetails: function() { console.log(`${this.name} - ${this.city}`); }};

下面是 TypeScript 文档中的一个例子。

这是一个 JS 对象,咱们使用接口IPerson给它加上类型:

letsuits = ["hearts","spades","clubs","diamonds"];

interface IPerson { name: string; city: string; age: number;}const tom: IPerson = { name: "web前端", city: "厦门", age: 26, printDetails: function() { console.log(`${this.name} - ${this.city}`); }};

functionpickCard(x: {suit: string; card: number; }[]):number;

编译代码并查看报错信息:

functionpickCard(x: number):{suit: string; card: number; };

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'.

functionpickCard(x):any{

IPerson没有任何名为printDetails的属性,但更重要的是它应该是一个函数。幸运的是,TypeScript 接口也可以描述函数。如下所示:

// Check to see if we're working with an object/array

interface IPerson { name: string; city: string; age: number; printDetails(): void;}

// if so, they gave us the deck and we'll pick the card

在这里,我们添加了类型函数的属性printDetails,返回void。void表示不返回任何值。

if(typeofx =="object") {

实际上,打印到控制台的函数不会返回任何内容。 如果要从printDetails返回字符串,则可以将返回类型调整为string:

letpickedCard =Math.floor(Math.random() * x.length);

interface IPerson { name: string; city: string; age: number; printDetails(): string;}const tom: IPerson = { name: "web前端", city: "厦门", age: 26, printDetails: function() { return `${this.name} - ${this.city}`; }};

returnpickedCard;

如果函数有参数呢? 在接口中,可以为它们添加类型注释

}

interface IPerson { name: string; city: string; age: number; printDetails(): string; anotherFunc(a: number, b: number): number;}

// Otherwise just let them pick the card

总结

elseif(typeofx =="number") {

这里无法涵盖每一个 TypeScript 特性。例如,省略[了ES2015类及其与接口或更高级类型]6的关系。当然后续会持续介绍。

letpickedSuit =Math.floor(x /13);

在这个 TypeScript 教程中,讲了:

return{ suit: suits[pickedSuit], card: x };

变量,函数参数和返回值的类型注释接口自定义类型类型别名

}

TS 帮助咱们减少一些 JS 代码隐藏的错误。需要重复的是,TypeScript 不能替代测试。 蛤它确实是一个有价值的工具,一开始很难掌握,但完全值得投资。

}

原文:

letmyDeck = [{ suit:"diamonds", card:2}, { suit:"spades", card:10}, { suit:"hearts", card:4}];

letpickedCard1 = myDeck[pickCard(myDeck)];

alert("card: " pickedCard1.card " of " pickedCard1.suit);

letpickedCard2 = pickCard(15);

alert("card: " pickedCard2.card " of " pickedCard2.suit);

这样至少在函数头的描述上清晰多了,而且函数的各个分派函数的类型定义也可以明确的标记出来了。

本文由必发88官网发布,转载请注明来源:项目中使用