导航


HTML

CSS

JavaScript

浏览器 & 网络

版本管理

框架

构建工具

TypeScript

性能优化

软实力

算法

UI、组件库

Node

冷门技能

转载自:https://juejin.cn/post/6872111128135073806

阿宝哥第一次使用 TypeScript 是在 Angular 2.x 项目中,那时候 TypeScript 还没有进入大众的视野。然而现在学习 TypeScript 的小伙伴越来越多了,本文阿宝哥将从 16 个方面入手,带你一步步学习 TypeScript,感兴趣的小伙伴不要错过。

Untitled

一、TypeScript 是什么

TypeScript 是一种由微软开发的自由和开源的编程语言。它是 JavaScript 的一个超集,而且本质上向这个语言添加了可选的静态类型和基于类的面向对象编程。

TypeScript 提供最新的和不断发展的 JavaScript 特性,包括那些来自 2015 年的 ECMAScript 和未来的提案中的特性,比如异步功能和 Decorators,以帮助建立健壮的组件。下图显示了 TypeScript 与 ES5、ES2015 和 ES2016 之间的关系:

Untitled

1.1 TypeScript 与 JavaScript 的区别

TypeScript JavaScript
JavaScript 的超集用于解决大型项目的代码复杂性 一种脚本语言,用于创建动态网页
可以在编译期间发现并纠正错误 作为一种解释型语言,只能在运行时发现错误
强类型,支持静态和动态类型 弱类型,没有静态类型选项
最终被编译成 JavaScript 代码,使浏览器可以理解 可以直接在浏览器中使用
支持模块、泛型和接口 不支持模块,泛型或接口
社区的支持仍在增长,而且还不是很大 大量的社区支持以及大量文档和解决问题的支持

1.2 获取 TypeScript

命令行的 TypeScript 编译器可以使用 npm 包管理器来安装。

1.安装 TypeScript

npm install -g typescript

2.验证 TypeScript

tsc -v
# Version 4.5.2

3.编译 TypeScript 文件

tsc helloworld.ts
# helloworld.ts => helloworld.js

当然,对刚入门 TypeScript 的小伙伴来说,也可以不用安装 typescript,而是直接使用线上的 TypeScript Playground 来学习新的语法或新特性。通过配置 TS Config 的 Target,可以设置不同的编译目标,从而编译生成不同的目标代码。

下图示例中所设置的编译目标是 ES5:

Untitled

⭐️ 1.3 典型 TypeScript 工作流程

Untitled

如你所见,在上图中包含 3 个 ts 文件:a.ts、b.ts 和 c.ts。这些文件将被 TypeScript 编译器,根据配置的编译选项编译成 3 个 js 文件,即 a.js、b.js 和 c.js。对于大多数使用 TypeScript 开发的 Web 项目,我们还会对编译生成的 js 文件进行打包处理,然后在进行部署。

1.4 TypeScript 初体验

新建一个 hello.ts 文件,并输入以下内容:

function greet(person: string) {
  return 'Hello, ' + person;
}

console.log(greet("TypeScript"));

然后执行 tsc hello.ts 命令,之后会生成一个编译好的文件 hello.js:

"use strict";
function greet(person) {
  return 'Hello, ' + person;
}
console.log(greet("TypeScript"));

观察以上编译后的输出结果,我们发现 person 参数的类型信息在编译后被擦除了。TypeScript 只会在编译阶段对类型进行静态检查,如果发现有错误,编译时就会报错。而在运行时,编译生成的 JS 与普通的 JavaScript 文件一样,并不会进行类型检查。

二、TypeScript 基础类型

2.1 Boolean 类型

let isDone: boolean = false;
// ES5:var isDone = false;

2.2 Number 类型

let count: number = 10;
// ES5:var count = 10;

2.3 String 类型

let name: string = "semliker";
// ES5:var name = 'semlinker';

2.4 Symbol 类型

const sym = Symbol();
let obj = {
  [sym]: "semlinker",
};

console.log(obj[sym]); // semlinker

⭐️ 2.5 Array 类型

let list: number[] = [1, 2, 3];
// ES5:var list = [1,2,3];

let list: Array<number> = [1, 2, 3]; // Array<number>泛型语法
// ES5:var list = [1,2,3];

⭐️ 2.6 Enum 类型

使用枚举我们可以定义一些带名字的常量。 使用枚举可以清晰地表达意图或创建一组有区别的用例。

TypeScript 支持数字的和基于字符串的枚举。

1.数字枚举

enum Direction {
  NORTH,
  SOUTH,
  EAST,
  WEST,
}

let dir: Direction = Direction.NORTH;

默认情况下,NORTH 的初始值为 0,其余的成员会从 1 开始自动增长。换句话说,Direction.SOUTH 的值为 1,Direction.EAST 的值为 2,Direction.WEST 的值为 3。

以上的枚举示例经编译后,对应的 ES5 代码如下:

"use strict";var Direction;(function (Direction) {  Direction[(Direction["NORTH"] = 0)] = "NORTH";  Direction[(Direction["SOUTH"] = 1)] = "SOUTH";  Direction[(Direction["EAST"] = 2)] = "EAST";  Direction[(Direction["WEST"] = 3)] = "WEST";})(Direction || (Direction = {}));var dir = Direction.NORTH;

当然我们也可以设置 NORTH 的初始值,比如:

enum Direction {
  NORTH = 3,
  SOUTH,
  EAST,
  WEST,
}

编辑后代码:

"use strict";
var Direction;
(function (Direction) {
    Direction[Direction["NORTH"] = 3] = "NORTH";
    Direction[Direction["SOUTH"] = 4] = "SOUTH";
    Direction[Direction["EAST"] = 5] = "EAST";
    Direction[Direction["WEST"] = 6] = "WEST";
})(Direction || (Direction = {}));

2.字符串枚举

在 TypeScript 2.4 版本,允许我们使用字符串枚举。在一个字符串枚举里,每个成员都必须用字符串字面量,或另外一个字符串枚举成员进行初始化。

enum Direction {
  NORTH = "NORTH",
  SOUTH = "SOUTH",
  EAST = "EAST",
  WEST = "WEST",
}

以上代码对应的 ES5 代码如下:

"use strict";
var Direction;
(function (Direction) {
    Direction["NORTH"] = "NORTH";
    Direction["SOUTH"] = "SOUTH";
    Direction["EAST"] = "EAST";
    Direction["WEST"] = "WEST";
})(Direction || (Direction = {}));

通过观察数字枚举和字符串枚举的编译结果,我们可以知道数字枚举除了支持 从成员名称到成员值 的普通映射之外,它还支持 从成员值到成员名称 的反向映射:

enum Direction {
  NORTH,
  SOUTH,
  EAST,
  WEST,
}

let dirName = Direction[0]; // NORTH
let dirVal = Direction["NORTH"]; // 0

另外,对于纯字符串枚举,我们不能省略任何初始化程序。而数字枚举如果没有显式设置值时,则会使用默认规则进行初始化。

3.常量枚举

除了数字枚举和字符串枚举之外,还有一种特殊的枚举 —— 常量枚举。它是使用 const 关键字修饰的枚举,常量枚举会使用内联语法,不会为枚举类型编译生成任何 JavaScript。为了更好地理解这句话,我们来看一个具体的例子:

const enum Direction {
  NORTH,
  SOUTH,
  EAST,
  WEST,
}

let dir: Direction = Direction.NORTH;

以上代码对应的 ES5 代码如下:

"use strict";
var dir = 0 /* NORTH */;

4.异构枚举

异构枚举的成员值是数字和字符串的混合:

enum Enum {
  A,
  B,
  C = "C",
  D = "D",
  E = 8,
  F,
}

以上代码对于的 ES5 代码如下:

"use strict";
var Enum;
(function (Enum) {
    Enum[Enum["A"] = 0] = "A";
    Enum[Enum["B"] = 1] = "B";
    Enum["C"] = "C";
    Enum["D"] = "D";
    Enum[Enum["E"] = 8] = "E";
    Enum[Enum["F"] = 9] = "F";
})(Enum || (Enum = {}));

通过观察上述生成的 ES5 代码,我们可以发现数字枚举相对字符串枚举多了 “反向映射”:

console.log(Enum.A) //输出:0
console.log(Enum[0]) // 输出:A

2.7 Any 类型

在 TypeScript 中,任何类型都可以被归为 any 类型。这让 any 类型成为了类型系统的顶级类型(也被称作全局超级类型)。

let notSure: any = 666;
notSure = "semlinker";
notSure = false;

any 类型本质上是类型系统的一个逃逸舱。作为开发者,这给了我们很大的自由:TypeScript 允许我们对 any 类型的值执行任何操作,而无需事先执行任何形式的检查。比如:

let value: any;

value.foo.bar; // OK
value.trim(); // OK
value(); // OK
new value(); // OK
value[0][1]; // OK

在许多场景下,这太宽松了。使用 any 类型,可以很容易地编写类型正确但在运行时有问题的代码。如果我们使用 any 类型,就无法使用 TypeScript 提供的大量的保护机制。为了解决 any 带来的问题,TypeScript 3.0 引入了 unknown 类型。

⭐️ 2.8 Unknown 类型

就像所有类型都可以赋值给 any,所有类型也都可以赋值给 unknown。这使得 unknown 成为 TypeScript 类型系统的另一种顶级类型(另一种是 any)。下面我们来看一下 unknown 类型的使用示例:

let value: unknown;

value = true; // OK
value = 42; // OK
value = "Hello World"; // OK
value = []; // OK
value = {}; // OK
value = Math.random; // OK
value = null; // OK
value = undefined; // OK
value = new TypeError(); // OK
value = Symbol("type"); // OK

value 变量的所有赋值都被认为是类型正确的。但是,当我们尝试将类型为 unknown 的值赋值给其他类型的变量时会发生什么?

let value: unknown;

let value1: unknown = value; // OK
let value2: any = value; // OK
let value3: boolean = value; // Error
let value4: number = value; // Error
let value5: string = value; // Error
let value6: object = value; // Error
let value7: any[] = value; // Error
let value8: Function = value; // Error

unknown 类型只能被赋值给 any 类型和 unknown 类型本身。直观地说,这是有道理的:只有能够保存任意类型值的容器才能保存 unknown 类型的值。毕竟我们不知道变量 value 中存储了什么类型的值。 现在让我们看看当我们尝试对类型为 unknown 的值执行操作时会发生什么。以下是我们在之前 any 章节看过的相同操作:

let value: unknown;

value.foo.bar; // Error
value.trim(); // Error
value(); // Error
new value(); // Error
value[0][1]; // Error

value 变量类型设置为 unknown 后,这些操作都不再被认为是类型正确的。通过将 any 类型改变为 unknown 类型,我们已将允许所有更改的默认设置,更改为禁止任何更改。

2.9 Tuple 类型

众所周知,数组一般由同种类型的值组成,但有时我们需要在单个变量中存储不同类型的值,这时候我们就可以使用元组。在 JavaScript 中是没有元组的,元组是 TypeScript 中特有的类型,其工作方式类似于数组。

元组可用于定义具有有限数量的未命名属性的类型。每个属性都有一个关联的类型。使用元组时,必须提供每个属性的值。为了更直观地理解元组的概念,我们来看一个具体的例子:

let tupleType: [string, boolean];
tupleType = ["semlinker", true];

在上面代码中,我们定义了一个名为 tupleType 的变量,它的类型是一个类型数组 [string, boolean],然后我们按照正确的类型依次初始化 tupleType 变量。与数组一样,我们可以通过下标来访问元组中的元素:

console.log(tupleType[0]); // semlinker
console.log(tupleType[1]); // true

在元组初始化的时候,如果出现类型不匹配的话,比如:

tupleType = [true, "semlinker"];

此时,TypeScript 编译器会提示以下错误信息:

[0]: Type 'true' is not assignable to type 'string'.
[1]: Type 'string' is not assignable to type 'boolean'.

很明显是因为类型不匹配导致的。在元组初始化的时候,我们还必须提供每个属性的值,不然也会出现错误,比如:

tupleType = ["semlinker"];

此时,TypeScript 编译器会提示以下错误信息: