乐趣区

关于前端:TypeScript-官方手册翻译计划一基础

  • 阐明 :目前网上没有 TypeScript 最新官网文档的中文翻译,所以有了这么一个翻译打算。因为我也是 TypeScript 的初学者,所以无奈保障翻译百分之百精确,若有谬误,欢送评论区指出;
  • 翻译内容 :暂定翻译内容为 TypeScript Handbook,后续有空会补充翻译文档的其它局部;
  • 我的项目地址 :TypeScript-Doc-Zh,如果对你有帮忙,能够点一个 star ~

本章节官网文档地址:The Basics

根底

欢送来到手册的第一章节。如果这是你第一次接触到 TypeScript,你可能须要先浏览一下入门指南

JavaScript 中的每个值会随着咱们执行不同的操作体现出一系列的行为。这听起来很形象,看上面的例子,考虑一下针对变量 message 可能执行的操作:

// 拜访 message 的 toLowerCase 办法并调用它
message.toLowerCase()
// 调用 message 函数
message()

第一行代码拜访了 messagetoLowerCase 办法并调用它;第二行代码则间接调用了 message 函数。

不过让咱们假如一下,咱们并不知道 message 的值 —— 这是很常见的一种状况,仅从下面的代码中咱们无奈确切得悉最终的后果。每个操作的后果齐全取决于 message 的初始值。

  • message 是否能够调用?
  • 它有 toLowaerCase 属性吗?
  • 如果有这个属性,那它能够调用吗?
  • 如果 message 以及它的属性都是能够调用的,那么别离返回什么?

在编写 JavaScript 代码的时候,这些问题的答案常常须要咱们本人记在脑子里,而且咱们必须得祷告本人解决好了所有细节。

假如 message 是这样定义的:

const message = 'Hello World!'

你可能很容易猜到,如果执行 message.toLowerCase(),咱们将会失去一个首字母小写的字符串。

如果执行第二行代码呢?相熟 JavaScript 的你必定猜到了,这会抛出一个异样:

TypeError: message is not a function

如果能够防止这样的谬误就好了。

当咱们执行代码的时候,JavaScript 运行时会计算出值的类型 —— 这种类型有什么行为和性能,从而决定采取什么措施。这就是下面的代码会抛出 TypeError 的起因 —— 它表明字符串 "Hello World!" 无奈作为函数被调用。

对于诸如 string 或者 number 这样的原始类型,咱们能够通过 typeof 操作符在运行时算出它们的类型。但对于像函数这样的类型,并没有对应的运行时机制去计算类型。举个例子,看上面的函数:

function fn(x){return x.flip()
}

从代码能够看出,仅当存在一个带有 flip 属性的对象时,这个函数才能够失常运行,但 JavaScript 无奈在代码执行时以一种咱们能够查看的形式传递这个信息。要让纯 JavaScript 通知咱们 fn 在给定特定参数的时候会做什么事,惟一的办法就是理论调用 fn 函数。这样的特色使得咱们很难在代码执行前进行相干的预测,也意味着咱们在编写代码的时候,很难搞分明代码会做什么事。

从这个角度看,所谓的类型其实就是形容了什么值能够平安传递给 fn,什么值会引起报错。JavaScript 只提供了动静类型 —— 执行代码,而后能力晓得会产生什么事。

那么无妨咱们改用一种计划,应用一个动态的类型零碎,在代码理论执行前预测代码的行为。

动态类型查看

还记得之前咱们将字符串作为函数调用时,抛出的 TypeError 谬误吗?大多数开发者在执行代码时不心愿看到任何谬误 —— 毕竟这些都是 bug!当咱们编写新代码的时候,咱们也会尽量避免引入新的 bug。

如果咱们只是增加了一点代码,保留文件,从新运行代码,而后马上看到报错,那么咱们或者能够疾速定位到问题 —— 但这种状况毕竟只是多数。咱们可能没有全面、彻底地进行测试,以至于没有发现一些潜在谬误!或者,如果咱们幸运地发现了这个谬误,咱们可能最终会进行大规模的重构,并增加许多不同的代码。

现实的计划应该是,咱们有一个工具能够在代码执行前找出 bug。而这正是像 TypeScript 这样的动态类型查看器所做的事件。动态类型零碎形容了程序运行时值的构造和行为。像 TypeScript 这样的动态类型查看器会利用类型零碎提供的信息,并在“事态倒退不对劲”的时候告知咱们。

const message = 'hello!';
message()
// This expression is not callable.
//    Type 'String' has no call signatures.

还是之前的代码,但这次应用的是 TypeScript,它会在编译的时候就抛出谬误。

非异样失败

目前为止,咱们探讨的都是运行时谬误 —— JavaScript 运行时通知咱们,它感觉某个中央有异样。这些异样之所以可能抛出,是因为 ECMAScript 标准 明确规定了针对异样应该体现的行为。

举个例子,标准指出,试图调用无奈调用的货色应该抛出一个谬误。兴许你会感觉这是“天经地义的”,并且你会感觉,拜访对象上不存在的属性时,也会抛出一个谬误。但恰恰相反,JavaScript 的体现和咱们的料想不同,它返回的是 undefined

const user = {
    name: 'Daniel',
    age: 26,
};
user.location;       // 返回 undefined

最终,咱们须要一个动态类型零碎来通知咱们,哪些代码在这个零碎中被标记为谬误的代码 —— 即便它是不会马上引起谬误的“无效”JavaScript 代码。在 TypeScript 中,上面的代码会抛出一个谬误,指出 location 没有定义:

const user = {
    name: 'Daniel',
    age: 26,
};
user.location;
// Property 'location' does not exist on type '{name: string; age: number;}'.

尽管有时候这意味着你须要在表白的内容上进行衡量,但咱们的目标是为了找到程序中更多非法的 bug。而 TypeScript 也确实能够捕捉到很多非法的 bug:

举个例子,拼写错误:

const announcement = "Hello World!";
 
// 你须要花多久能力留神到拼写错误?announcement.toLocaleLowercase();
announcement.toLocalLowerCase();
 
// 实际上正确的拼写是这样的
announcement.toLocaleLowerCase();

未调用的函数:

function flipCoin(){// 其实应该应用 Math.random()
    return Math.random < 0.5
}
// Operator '<' cannot be applied to types '() => number' and 'number'.

或者是根本的逻辑谬误:

const value = Math.random() < 0.5 ? "a" : "b";
if (value !== "a") {// ...} else if (value === "b") {// 永远无奈达到这个分支}

类型工具

TypeScript 能够在咱们的代码呈现谬误时捕捉 bug。这很好,但更要害的是,它可能在一开始就避免咱们的代码呈现谬误。

类型查看器能够通过获取的信息查看咱们是否正在拜访变量或者其它属性上的正确属性。同时,它也能凭借这些信息提醒咱们可能想要拜访的属性。

这意味着 TypeScript 也能用于编辑代码。咱们在编辑器中输出的时候,外围的类型查看器可能提供报错信息和代码补全。人们常常谈判到 TypeScript 在工具层面的作用,这就是一个典型的例子。

import express from "express";
const app = express();
 
app.get("/", function (req, res) {
  // 在拼写 send 办法的时候,这里会有代码补全的提醒
  // res.sen...         
});
 
app.listen(3000);

TypeScript 在工具层面的作用十分弱小,远不止拼写时进行代码补全和错误信息提醒。反对 TypeScript 的编辑器能够通过“疾速修复”性能主动修复谬误,重构产生易组织的代码。同时,它还具备无效的导航性能,可能让咱们跳转到某个变量定义的中央,或者找到对于给定变量的所有援用。所有这些性能都建设在类型查看器上,并且是跨平台的,因而你最喜爱的编辑器很可能也反对了 TypeScript。

TypeScript 编译器 —— tsc

咱们始终在探讨类型查看器,但目前为止还没上手应用过。是时候和咱们的新敌人 —— TypeScript 编译器 tsc 打交道了。首先,通过 npm 进行装置。

npm install -g typescript

这将全局装置 TypeScript 的编译器 tsc。如果你更偏向于装置在本地的 node_modules 文件夹中,那你可能须要借助 npx 或者相似的工具能力便捷地运行 tsc 指令。

当初,咱们新建一个空文件夹,尝试编写第一个 TypeScript 程序 hello.ts 吧。

// 和世界打个招呼
console.log('Hello world!');

留神这行代码没有任何多余的润饰,它看起来就和应用 JavaScript 编写的“hello world”程序截然不同。当初,让咱们运行 typescript 安装包自带的 tsc 指令进行类型查看吧。

tsc hello.ts

看!

等等,“看”什么呢?咱们运行了 tsc 指令,但如同也没产生什么事!是的,毕竟这行代码没有类型谬误,所以控制台中当然看不到报错信息的输入。

不过再检查一下 —— 你会发现输入了一个新的文件。在当前目录下,除了 hello.ts 文件外还有一个 hello.js 文件,而后者是 tsc 通过编译失去的纯 JavaScript 文件。查看 hello.js 文件的内容,咱们能够看到 TypeScript 编译器解决完 .ts 文件后产出的内容:

// 和世界打个招呼
console.log('Hello world!');

在这个例子中,TypeScript 简直没有须要转译的内容,所以转译前后的代码看起来截然不同。编译器总是试图产出清晰可读的代码,这些代码看起来就像失常的开发者编写的一样。尽管这不是一件容易的事件,但 TypeScript 始终保持缩进,关注跨行的代码,并且会尝试保留正文。

如果咱们刻意引入了一个会在类型查看阶段抛出的谬误呢?尝试改写 hello.ts 的代码如下:

function greet(person, date) {console.log(`Hello ${person}, today is ${date}!`);
}

greet("Brendan");

如果咱们再次执行 tsc hello.ts,那么控制台会抛出一个谬误!

Expected 2 arguments, but got 1.

TypeScript 通知咱们,咱们少传了一个参数给 greet 函数 —— 这个报错是十分正当的。目前为止,咱们编写的依然是规范的 JavaScript 代码,但类型查看仍然能够发现咱们代码中的问题。感激 TypeScript!

报错时仍产出文件

有一件事你可能没有留神到,在下面的例子中,咱们的 hello.js 文件再次发生了改变。关上这个文件,你会发现内容和输出的文件内容是一样的。这可能有点出其不意,明明 tsc 方才报错了啊,为什么还是能够编译产出文件呢?但这种后果其实和 TypeScript 的外围准则无关:大多数时候,开发者比 TypeScript 更理解代码。

再次重申,对代码进行类型查看,会限度能够运行的程序的品种,因而类型查看器会进行衡量,以确定哪些代码是能够被承受的。大多数时候这样没什么问题,但有的时候,这些查看会对咱们造成妨碍。举个例子,设想你当初正把 JavaScript 代码迁徙到 TypeScript 代码,并产生了很多类型查看谬误。最初,你不得不破费工夫解决类型查看器抛出的谬误,但问题在于,原始的 JavaScript 代码自身就是能够运行的!为什么把它们转换为 TypeScript 代码之后,反而就不能运行了呢?

所以在设计上,TypeScript 并不会对你造成妨碍。当然,随着工夫的推移,你可能心愿对谬误采取更具防御性的措施,同时也让 TypeScript 采取更加严格的行为。在这种状况下,你能够开启 noEmitOnError 编译选项。尝试批改你的 hello.ts 文件,并应用参数去运行 tsc 指令:

tsc --noEmitOnError hello.ts

当初你会发现,hello.js 没有再产生改变了。

显式类型

目前为止,咱们还没有通知 TypeScript persondate 是什么。批改一下代码,申明 personstring 类型,dataDate 对象。咱们也会通过 date 去调用 toDateString 办法。

function greet(person: string, date: Date) {console.log(`Hello ${person}, today is ${date.toDateString()}!`);
}

咱们所做的事件,是给 persondate 增加类型注解,形容 greet 调用的时候应该承受什么类型的参数。你能够将这个签名解读为“greet 承受 string 类型的 person,以及 Date 类型的 date”。

有了类型注解之后,TypeScript 就能通知咱们,哪些状况下对于 greet 的调用可能是不正确的。比方:

function greet(person: string, date: Date) {console.log(`Hello ${person}, today is ${date.toDateString()}!`);
}
 
greet("Maddison", Date());
// Argument of type 'string' is not assignable to parameter of type 'Date'.

TypeScript 报错提醒第二个参数有问题。为什么呢?

因为在 JavaScript 中间接调用 Date 办法返回的是字符串,而通过 new 去调用,则能够如预期那样返回一个 Date 对象。

不管怎样,咱们能够疾速修复这个谬误:

function greet(person: string, date: Date) {console.log(`Hello ${person}, today is ${date.toDateString()}!`);
}
 
greet("Maddison", new Date());

记住,咱们并不总是须要显式地进行类型注解。在很多状况下,即便省略了类型注解,TypeScript 也能够为咱们推断出类型。

let msg = 'hello there!';
  //^^^
  // let msg: string   

即便咱们没有通知 TypeScript msg 是一个 string 类型的变量,它也可能本人进行推断。这是一个个性,在类型零碎可能正确地进行类型推断的时候,最好不要手动增加类型注解了。

留神:在编辑器中,将鼠标放到变量下面,会有对于变量类型的提醒

抹除类型

咱们看一下 greet 通过编译后产出的 JavaScript 代码是什么样的:

"use strict";
function greet(person, date) {console.log("Hello" + person + ", today is" + date.toDateString() + "!");
}
greet("Maddison", new Date());

能够留神到有两个变动:

  1. persondate 参数的类型注解不见了
  2. 模板字符串变成了通过 + 拼接的字符串

稍后再解释第二点,咱们先来看第一个变动。类型注解并不属于 JavaScript 或者 ECMAScript 的内容,所以没有任何浏览器或者运行时可能间接执行不经解决的 TypeScript 代码。这也是为什么 TypeScript 首先须要一个编译器 —— 它须要通过编译,能力去除或者转换 TypeScript 独有的代码,从而让这些代码能够在浏览器上运行。大多数 TypeScript 独有的代码都会被抹除,在这个例子中,能够看到类型注解的代码齐全被抹除了。

记住: 类型注解永远不会改变程序在运行时的行为

降级

另一个变动就是咱们的模板字符串从:

`Hello ${person}, today is ${date.toDateString()}!`;

变成了:

"Hello" + person + ", today is" + date.toDateString() + "!";

为什么会这样子呢?

模板字符串是 ECMAScript 2015(或者 ECMAScript6、ES2015、ES6 等)引入的新个性。TypeScript 能够将高版本 ECMAScript 的代码重写为相似 ECMAScript3 或者 ECMAScript5(也就是 ES3 或者 ES5)这样较低版本的代码。相似这样将更新或者“更高”版本的 ECMAScript 向下降级为更旧或者“更低”版本的代码,就是所谓的“降级”。

默认状况下,TypeScript 会转化为 ES3 代码,这是一个十分旧的 ECMAScript 版本。咱们能够应用 target 选项将代码往较新的 ECMAScript 版本转换。通过应用 --target es2015 参数,咱们能够失去 ECMAScript2015 版本的指标代码,这意味着这些代码可能在反对 ECMAScript2015 的环境中执行。因而,运行 tsc --target es2015 hello.ts 之后,咱们会失去如下代码:

function greet(person, date) {console.log(`Hello ${person}, today is ${date.toDateString()}!`);
}
greet("Maddison", new Date());

尽管默认的指标代码采纳的是 ES3 语法,但当初浏览器大多数都曾经反对 ES2015 了。所以,开发者能够平安地指定指标代码采纳 ES2015 或者是更高的 ES 版本,除非你须要着重兼容某些古老的浏览器。

严格性

不同的用户会因为不同的理由去抉择应用 TypeScript 的类型查看器。一些用户寻求的是一种更加涣散、可选的开发体验,他们心愿类型查看仅作用于局部代码,同时还可享受 TypeScript 提供的性能。这也是 TypeScript 默认提供的开发体验,类型是可选的,推断会应用最涣散的类型,对于潜在的 null/undefined 类型的值也不会进行查看。就像 tsc 在编译报错的状况下依然可能失常产出文件一样,这些默认的配置会确保不对你的开发过程造成妨碍。如果你正在迁徙现有的 JavaScript 代码,那么这样的配置可能刚好适宜。

另一方面,大多数的用户更心愿 TypeScript 能够疾速地、尽可能多地查看代码,这也是这门语言提供了严格性设置的起因。这些严格性设置将动态的类型查看从一种切换开关的模式(对于你的代码,要么全副进行查看,要么齐全不查看)转换为靠近于刻度盘那样的模式。你越是转动它,TypeScript 就会为你查看越多货色。这可能须要额定的工作,但从久远来看,这是值得的,它能够带来更彻底的查看以及更精密的工具。如果可能,新我的项目应该始终启用这些严格性配置。

TypeScript 有几个和类型查看相干的严格性设置,它们能够随时关上或敞开,如若没有非凡阐明,咱们文档中的例子都是在开启所有严格性设置的状况下执行的。CLI 中的 strict 配置项,或者 tsconfig.json 中的 "strict: true" 配置项,能够一次性开启全副严格性设置。当然,咱们也能够独自开启或者敞开某个设置。在所有这些设置中,尤其须要关注的是 noImplicitAny 和 strictNullChecks。

noImplicitAny

回忆一下,在后面的某些例子中,TypeScript 没有为咱们进行类型推断,这时候变量会采纳最宽泛的类型:any。这并不是一件最蹩脚的事件 —— 毕竟,应用 any 类型根本就和纯 JavaScript 一样了。

然而,应用 any 通常会和应用 TypeScript 的目标相违反。你的程序应用越多的类型,那么在验证和工具上你的收益就越多,这意味着在编码的时候你会遇到越少的 bug。启用 noImplicitAny 配置项,在遇到被隐式推断为 any 类型的变量时就会抛出一个谬误。

strictNullChecks

默认状况下,nullundefined 能够被赋值给其它任意类型。这会让你的编码更加容易,但世界上有数多的 bug 正是因为遗记解决 nullundefined 导致的 —— 有时候它甚至会带来数十亿美元的损失!strictNullChecks 配置项让解决 nullundefined 的过程更加显著,会让咱们时刻注意本人是否遗记解决 nullundefined

退出移动版