TypeScript 官方手册翻译计划【一】:基础

2021年11月22日 阅读数:10
这篇文章主要向大家介绍TypeScript 官方手册翻译计划【一】:基础,主要内容包括基础应用、实用技巧、原理机制等方面,希望对大家有所帮助。
  • 说明:目前网上没有 TypeScript 最新官方文档的中文翻译,因此有了这么一个翻译计划。由于我也是 TypeScript 的初学者,因此没法保证翻译百分之百准确,如有错误,欢迎评论区指出;
  • 翻译内容:暂定翻译内容为 TypeScript Handbook,后续有空会补充翻译文档的其它部分;
  • 项目地址TypeScript-Doc-Zh,若是对你有帮助,能够点一个 star ~

本章节官方文档地址:The Basicsnode

基础

欢迎来到手册的第一章节。若是这是你第一次接触到 TypeScript,你可能须要先阅读一下 入门指南

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

// 访问 message 的 toLowerCase 方法并调用它
message.toLowerCase()
// 调用 message 函数
message()

第一行代码访问了 messagetoLowerCase 方法并调用它;第二行代码则直接调用了 message 函数。express

不过让咱们假设一下,咱们并不知道 message 的值 —— 这是很常见的一种状况,仅从上面的代码中咱们没法确切得知最终的结果。每一个操做的结果彻底取决于 message 的初始值。npm

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

在编写 JavaScript 代码的时候,这些问题的答案常常须要咱们本身记在脑子里,并且咱们必须得祈祷本身处理好了全部细节。json

假设 message 是这样定义的:segmentfault

const message = 'Hello World!'

你可能很容易猜到,若是执行 message.toLowerCase(),咱们将会获得一个首字母小写的字符串。浏览器

若是执行第二行代码呢?熟悉 JavaScript 的你确定猜到了,这会抛出一个异常:安全

TypeError: message is not a function

若是能够避免这样的错误就行了。bash

当咱们执行代码的时候,JavaScript 运行时会计算出值的类型 —— 这种类型有什么行为和功能,从而决定采起什么措施。这就是上面的代码会抛出 TypeError 的缘由 —— 它代表字符串 "Hello World!" 没法做为函数被调用。app

对于诸如 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(或者 ECMAScript六、ES201五、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" 配置项,能够一次性开启所有严格性设置。固然,咱们也能够单独开启或者关闭某个设置。在全部这些设置中,尤为须要关注的是 noImplicitAnystrictNullChecks

noImplicitAny

回想一下,在前面的某些例子中,TypeScript 没有为咱们进行类型推断,这时候变量会采用最宽泛的类型:any。这并非一件最糟糕的事情 —— 毕竟,使用 any 类型基本就和纯 JavaScript 同样了。

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

strictNullChecks

默认状况下,nullundefined 能够被赋值给其它任意类型。这会让你的编码更加容易,但世界上无数多的 bug 正是因为忘记处理 nullundefined 致使的 —— 有时候它甚至会带来数十亿美圆的损失strictNullChecks 配置项让处理nullundefined 的过程更加明显,会让咱们时刻留意本身是否忘记处理 nullundefined