# 到底为什么要学习 TypeScript?

  • 为大型系统而生:TypeScript 是静态类型化的 JavaScript 超集,在大型工程中有无可比拟的优势,是开发大型系统的必备良药,VSCode、Vue 3.0、Angular都是由TS开发
  • 招聘市场需求陡增:从2018年开始 TypeScript 就成为GitHub前10的语言,已经有大量的大厂团队采用 TypeScript 开发,很多招聘要求上有了 TypeScript 的身影,或者必备或者加分项
  • Vue3.0发布在即:统治前端的三大框架Angular、React、Vue,Angular本身就是TS最早的支持者,React 对 TS 支持友好,非常多的团队开始 TS 化,Vue3.0一旦发布,依赖前端框架的业务开发基本上就离不开 TS 了
  • TypeScript 是一门静态语言。这也是为什么 「TypeScript 必定赴 coffeescript 后尘,会被标准取代」 这个论断几乎不可能成立的原因之一,coffeescript 本质上是 JavaScript 的语法糖化,ES2015 参考了大量 coffeescript 的内容进行了标准化,因此 coffeescript 的优势也就不存在了,被淘汰在所难免。给一门语言加语法糖是相对容易推进到标准的事情,而直接把一门语言从动态改为静态,还要兼容数以亿计的老旧网站,这个在可预见的时间内几乎不可能发生,TypeScript 与 coffeescript 虽然都是 「Compile to JavaScript Language」,但是 TypeScript 的静态性是它立于不败之地的基础。

(虽然 Angular 和 Vue 都声称支持 js 开发,但是由于本身用 TS 编写,后续的生态也基于 TS,基本上很少人再用 js 编写相关代码)

# TS开发者的四个层级

1、业务使用者: 这个层级的开发者可以在业务代码中熟练利用 TypeScript 编码,但是无法进行类型编程,也无法写出一些底层库,仅仅停留在使用阶段

2、类型编程者: 这个层级的开发者可以对类型进行编程,可以开发出一些实用的工具类型,对于难以定义的类型也能驾轻就熟

3、TS定制者: 这个层级的开发者对 TypeScript 的类型系统比较熟悉,对 TypeScript 的语言设计也有一定的认知,可以开发TypeScript Transformer Plugin来定制化开发 TypeScript

4、TS设计者: 这个层面的开发者可以参与到 TypeScript 这门语言的设计中去,基本上能达到PL领域的从业人员的水准

# 静态类型

  • 编程语言的静态类型定义在学术上理解起来比较复杂,简单来说,一门语言在编译时报错,那么是静态语言,如果在运行时报错,那么是动态语言。

  • 这里还有纠正一个概念,TypeScript 是静态弱类型语言,这跟C语言是一样的,并不是所谓的强类型,因为要兼容 JavaScript, 所以 TypeScript 几乎不限制 JavaScript 中原有的隐式类型转换,它对类型的隐式转换是有容忍度的,而真正的静态强类型语言比如 Java、C# 是不会容忍隐式转换的。

  • 那么为什么静态类型是 TypeScript 的杀手锏呢? 归根到底,任何能提升生产力的东西都会被加入工程化的浪潮中,更何况一门静态类型的语言对生产力的提升是质的。

  • 我们先看看在 JavaScript 项目中最常见的十大错误。

An image

我们是不是耳熟能详?这些低级错误占用了大量 debug 和 google 的时间,而如果你用 TypeScript 可以在编写阶段就规避了,自从我们用了 TypeScript 之后,低级报错基本就没犯过,大多数情况下是我们自身编写的程序逻辑错误。

很多项目,尤其是中大型项目,我们是需要团队多人协作的,那么如何保证协作呢?这个时候可能需要大量的文档和注释,显式类型就是最好的注释,而通过 TypeScript 提供的类型提示功能我们可以非常舒服地调用同伴的代码,由于 TypeScript 的存在我们可以节省大量沟通成本、代码阅读成本等等。

所以我对这句话「静态类型不是银弹,大型项目依然可以用 JavaScript 编写」持怀疑态度,静态类型固然不是银弹,但是它对多人协作的大型项目的友好程度是远超动态语言的,我们既然有了更好的工具为什么不用呢?

而实际上各大用动态语言的项目在这些年随着项目复杂度增加,开始逐渐进行静态化,有人会问「谷歌、Facebook 不都还在用 js、php、Python吗?」

其实不是的,这些大厂用的 php、Python、js 跟普通人用的不一样,比如 Facebook 的 js 背后有一个叫做 FlowType 的静态类型检查器,React 和 Vue 2.x 就是用的这个类型检查器,再比如 Facebook 的 Python,他背后也有一个叫做pyre-check 的静态类型检查器,实际上是虽然他们在用动态语言,但是大厂养活着一个庞大的团队来开发各种静态分析工具把动态语言变成静态的。

# 严谨不失灵活

很多人以为用了 TypeScript 之后就会丧失 JavaScript 的灵活性,其实并不是。

首先,我们得承认 JavaScript 的灵活性对于中大型项目弊远远大于利,其次,TypeScript 由于兼容 JavaScript 所以其灵活度可以媲美 JavaScript,比如你可以把任何想灵活的地方将类型定义为 any 即可,把 TypeScript 变为 AnyScript 就能保持它的灵活度,毕竟TypeScript 对类型的检查严格程度是可以通过 tsconfig.json 来配置的。

即使在开启 strict 状态下的 TypeScript 依然是很灵活的,因为为了兼容 JavaScript,TypeScript 采用了Structural Type System。

因此,TypeScript 并不是类型定义本身,而是类型定义的形状(Shape),我们看个例子:

class Foo {
  method(input: string): number { ... }
}

class Bar {
  method(input: string): number { ... }
}

const foo: Foo = new Foo(); // Okay.
const bar: Bar = new Foo(); // Okay.
1
2
3
4
5
6
7
8
9
10

以上代码是不会报错的,因为他们的「形状」是一样的,而类似的代码在 Java 或者 C# 中是会报错的。 这就是 TypeScript 类型系统设计之初就考虑到了 JavaScript 灵活性,专门选择了 Structural Type System(结构类型系统)。

# TypeScript 没有缺点吗?

我们说了这么多 TypeScript 相比 JavaScript 的优势,难道 TypeScript 是没有缺点的吗?

严格来讲,TypeScript 的缺点相比于它带来的生产力上的提升是可以忽略不计的,而一些缺点并不是 TypeScript 本身带来的。

比如,与实际框架结合会有很多坑,我们一开始学习在框架中运用 TypeScript 的时候会花大量的时间在踩坑上,因为我们需要额外学习框架定义的 d.ts,而单单是官方入门文档的知识又不足以让我们很舒服地编写代码

比如,配置学习成本高,目前的前端脚手架基本上是以 JavaScript 为主,虽然 TypeScript 已经非常火了,但是相比于 JavaScript 多年积累的生态还是稍显不如,因此很多时候我们需要自己定制脚手架,比如同样是用 ESLint 做语法检查,其配置方式跟JavaScript完全不同,需要额外学习成本,配置 TypeScript 的 tsconfig.json ,用 ts-plugin-import 进行按需引入等等,都是需要额外学习成本的

再比如,TypeScript 的类型系统其实比较复杂,又是额外的学习成本,比如下面的类型定义,只通过官方文档你是不知道在干什么的:

An image

# 开始使用 TypeScript

在开始使用 TypeScript 前你最好有以下准备:

  • Node.js > 8.0,最好是最新的稳定版(目前是V10.16.3 )
  • 一个包管理工具 npm 或者 yarn
  • 一个文本编辑器或者 IDE (笔者的是 vscode)
  • 相关的 shell 命令仅适用于 *nix 系统,windows 系统不适用

# 安装 TypeScript

TypeScript 的安装很简单,你可以通过npm直接在全局安装 TypeScript。

> npm install -g typescript
1

# 创建环境

随后我们要创建一个目录:

mkdir ts-study && cd ts-study

接着创建 src 目录:

mkdir src && touch src/index.ts

接着我们用npm将目录初始化:

npm init

此时我们要使用 TypeScript 的话通常也需要初始化:

tsc --init

这个时候你会发现目录下多了一个tsconfig.json文件.

这是 TypeScript 的配置文件,里面已经包含官方初始化的一些配置以及注释,我们现在进行自定义的配置:

{
  "compilerOptions": {
    "target": "es5",                            // 指定 ECMAScript 目标版本: 'ES5'
    "module": "commonjs",                       // 指定使用模块: 'commonjs', 'amd', 'system', 'umd' or 'es2015'
    "moduleResolution": "node",                 // 选择模块解析策略
    "experimentalDecorators": true,             // 启用实验性的ES装饰器
    "allowSyntheticDefaultImports": true,       // 允许从没有设置默认导出的模块中默认导入。
    "sourceMap": true,                          // 把 ts 文件编译成 js 文件的时候,同时生成对应的 map 文件
    "strict": true,                             // 启用所有严格类型检查选项
    "noImplicitAny": true,                      // 在表达式和声明上有隐含的 any类型时报错
    "alwaysStrict": true,                       // 以严格模式检查模块,并在每个文件里加入 'use strict'
    "declaration": true,                        // 生成相应的.d.ts文件
    "removeComments": true,                     // 删除编译后的所有的注释
    "noImplicitReturns": true,                  // 不是函数的所有返回路径都有返回值时报错
    "importHelpers": true,                      // 从 tslib 导入辅助工具函数
    "lib": ["es6", "dom"],                      // 指定要包含在编译中的库文件
    "typeRoots": ["node_modules/@types"],
    "outDir": "./dist",
    "rootDir": "./src"
  },
  "include": [                                  // 需要编译的ts文件一个*表示文件匹配**表示忽略文件的深度问题
    "./src/**/*.ts"
  ],
  "exclude": [
    "node_modules",
    "dist",
    "**/*.test.ts",
  ]
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29

然后在package.json中加入我们的script命令:

{
  "name": "ts-study",
  "version": "1.0.0",
  "description": "",
  "main": "src/index.ts",
  "scripts": {
    "build": "tsc", // 编译
    "build:w": "tsc -w" // 监听文件,有变动即编译
  },
  "author": "",
  "license": "ISC",
  "devDependencies": {
    "typescript ": "^3.6.4"
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

# 编写第一个 TypeScript 程序

在src/index.ts中输入以下代码:

function greeter(person) {
    return "Hello, " + person
}

const user = "Jane User"
1
2
3
4
5

这个时候你会看到一个警告,这个警告在官方默认配置中是不会出现的,正是由于我们开启了 noImplicitAny 选项,对于隐式含有 any 类型的参数或者变量进行警告⚠️.

An image

之所以一开始就开启严格模式,是因为一旦你开始放任any类型的泛滥,就会把 TypeScript 变成 AnyScript ,会很难改掉这个恶习,所以从一开始就要用规范的 TypeScript 编码习惯。

我们进行修改如下:

function greeter(person: string) {
    return "Hello, " + person
}
1
2
3

此时我们可以看到,greeter函数自动加上了返回值类型,这是 TypeScript 自带的类型推导。

An image

# Typescript 的原始类型

如果你了解 JavaScript 的基础类型,那么这一节你会很好理解。

TypeScript的原始类型包括: boolean、number、string、void、undefined、null、symbol、bigint。

# 布尔类型

我们用 boolean 来表示布尔类型,注意开头是小写的,如果你在Typescript文件中写成 Boolean 那代表是 JavaScript 中的布尔对象,这是新手常犯的错误。

const isLoading: boolean = false 这里需要提示一下,很多 TypeScript 的原始类型比如 boolean、number、string等等,在JavaScript中都有类似的关键字 Boolean、Number、String,后者是 JavaScript 的构造函数,比如我们用 Number 用于数字类型转化或者构造 Number 对象用的,而 TypeScript 中的 number 类型仅仅是表示类型,两者完全不同。

数字 JavaScript中的二进制、十进制、十六进制等数都可以用 number 类型表示。

const decLiteral: number = 6
const hexLiteral: number = 0xf00d
const binaryLiteral: number = 0b1010
const octalLiteral: number = 0o744
1
2
3
4

# 字符串

const book: string = '深入浅出 Typescript'
1

# 空值

表示没有任何类型,当一个函数没有返回值时,你通常会见到其返回值类型是 void:

function warnUser(): void {
    alert("This is my warning message");
}
1
2
3

实际上只有null和undefined可以赋给void:

const a: void = undefined
1

Null 和 Undefined TypeScript 里,undefined 和 null 两者各自有自己的类型分别叫做 undefined 和 null,和void相似,它们的本身的类型用处不是很大:

let a: undefined = undefined;
let b: null = null;
1
2

默认情况下 null 和 undefined 是所有类型的子类型,就是说你可以把 null 和 undefined 赋值给 number 类型的变量。

但是在正式项目中一般都是开启 --strictNullChecks 检测的,即 null 和 undefined 只能赋值给 any 和它们各自(一个例外是 undefined 是也可以分配给void),可以规避非常多的问题。

# Symbol

注意:我们在使用 Symbol 的时候,必须添加 es6 的编译辅助库,如下:

"lib": ["es6", "dom"],                      // 指定要包含在编译中的库文件
1

Symbol 是在ES2015之后成为新的原始类型,它通过 Symbol 构造函数创建:

const sym1 = Symbol('key1');
const sym2 = Symbol('key2');
1
2

而且 Symbol 的值是唯一不变的:

Symbol('key1') === Symbol('key1') // false
1

# BigInt

BigInt 类型在 TypeScript3.2 版本被内置,使用 BigInt 可以安全地存储和操作大整数,即使这个数已经超出了JavaScript构造函数 Number 能够表示的安全整数范围。

注意:我们在使用 BigInt 的时候,必须添加 ESNext 的编译辅助库,如下:

"lib": ["es6", "dom", "ESNext"], 
1

在 JavaScript 中采用双精度浮点数,这导致精度有限,比如 Number.MAX_SAFE_INTEGER 给出了可以安全递增的最大可能整数,即2**53-1,我们看一下案例:

const max = Number.MAX_SAFE_INTEGER;

const max1 = max + 1
const max2 = max + 2

max1 === max2 //true
1
2
3
4
5
6

max1与max2居然相等?这就是超过精读范围造成的问题,而BigInt正是解决这类问题而生的:

// 注意,这里是 JavaScript 代码,并不是 typescript
const max = BigInt(Number.MAX_SAFE_INTEGER);

const max1 = max + 1n
const max2 = max + 2n

max1 === max2 // false
1
2
3
4
5
6
7

值得注意的是我们需要用 BigInt(number) 把 Number 转化为 BigInt,同时如果类型是 BigInt ,那么数字后面需要加 n ,就如同上面例子的 const max1 = max + 1n 中的 1n。

在TypeScript中,number 类型虽然和 BigInt 都是有表示数字的意思,但是实际上两者类型是不同的:

declare let foo: number;
declare let bar: bigint;

foo = bar; // error: Type 'bigint' is not assignable to type 'number'.
bar = foo; // error: Type 'number' is not assignable to type 'bigint'.
1
2
3
4
5

我们总结一下 TypeScript 中的原始类型:

  • 布尔类型:boolean
  • 数字类型:number
  • 字符串类型:string
  • 空值:void
  • Null 和 Undefined:null 和 undefined
  • Symbol 类型:symbol
  • BigInt 大数整数类型:bigint

# Typescript 中其他常见类型

# any

# unknown

unknown 是 TypeScript 3.0 引入了新类型,是 any 类型对应的安全类型。

unknown 和 any 的主要区别是 unknown 类型会更加严格:在对unknown类型的值执行大多数操作之前,我们必须进行某种形式的检查,而在对 any 类型的值执行操作之前,我们不必进行任何检查。

看看它们的区别在哪里

let value: any;

value.foo.bar;  // OK
value();        // OK
new value();    // OK
value[0][1];    // OK
1
2
3
4
5
6

如果是 unknown 类型,那么结果大不相同:

let value: unknown;

value.foo.bar;  // ERROR
value();        // ERROR
new value();    // ERROR
value[0][1];    // ERROR
1
2
3
4
5
6

我们看到,这就是 unknown 与 any 的不同之处,虽然它们都可以是任何类型,但是当 unknown 类型被确定是某个类型之前,它不能被进行任何操作比如实例化、getter、函数执行等等。

# never

never 类型表示的是那些永不存在的值的类型,never 类型是任何类型的子类型,也可以赋值给任何类型;然而,没有类型是 never 的子类型或可以赋值给 never 类型(除了never本身之外)。

即使any也不可以赋值给never。1

两个场景中 never 比较常见:

// 抛出异常的函数永远不会有返回值
function error(message: string): never {
    throw new Error(message);
}

// 空数组,而且永远是空的
const empty: never[] = []
1
2
3
4
5
6
7

# 数组

数组有两种类型定义方式,一种是使用泛型:

const list: Array<number> = [1, 2, 3]
1

另一种使用更加广泛那就是在元素类型后面接上 []:

const list: number[] = [1, 2, 3]
1

# 元组

元组类型与数组类型非常相似,表示一个已知元素数量和类型的数组,各元素的类型不必相同。

比如,你可以定义一对值分别为string和number类型的元组。

let x: [string, number];
x = ['hello', 10, false] // Error
x = ['hello'] // Error
1
2
3

我们看到,这就是元组与数组的不同之处,元组的类型如果多出或者少于规定的类型是会报错的,必须严格跟事先声明的类型一致才不会报错 那么有人会问,我们的类型完全一致,只是顺序错了有没有问题,比如上个例子中我们把 string、number 调换位置:

let x: [string, number];
x = ['hello', 10]; // OK
x = [10, 'hello']; // Error
1
2
3

我们看到,元组非常严格,即使类型的顺序不一样也会报错。

元组中包含的元素,必须与声明的类型一致,而且不能多、不能少,甚至顺序不能不符。

我们可以把元组看成严格版的数组,比如[string, number]我们可以看成是:

interface Tuple extends Array<string | number> {
  0: string;
  1: number;
  length: 2;
}
1
2
3
4
5

元组继承于数组,但是比数组拥有更严格的类型检查。

此外,还有一个个元组越界问题,比如 Typescript 允许向元组中使用数组的push方法插入新元素:

const tuple: [string, number] = ['a', 1];
tuple.push(2); // ok
console.log(tuple); // ["a", 1, 2] -> 正常打印出来
1
2
3

但是当我们访问新加入的元素时,会报错:

console.log(tuple[2]); // Tuple type '[string, number]' of length '2' has no element at index '2'
1

# Object

object 表示非原始类型,也就是除 number,string,boolean,symbol,null 或 undefined 之外的类型。

enum Direction {
    Center = 1
}

let value: object

value = Direction
value = [1]
value = [1, 'hello']
value = {}
1
2
3
4
5
6
7
8
9
10

我们看到,普通对象、枚举、数组、元组通通都是 object 类型。

# 深入理解枚举类型

枚举类型是很多语言都拥有的类型,它用于声明一组命名的常数,当一个变量有几种可能的取值时,可以将它定义为枚举类型。

# 数字枚举

当我们声明一个枚举类型是,虽然没有给它们赋值,但是它们的值其实是默认的数字类型,而且默认从0开始依次累加:

enum Direction {
    Up,
    Down,
    Left,
    Right
}

console.log(Direction.Up === 0); // true
console.log(Direction.Down === 1); // true
console.log(Direction.Left === 2); // true
console.log(Direction.Right === 3); // true
1
2
3
4
5
6
7
8
9
10
11

因此当我们把第一个值赋值后,后面也会根据第一个值进行累加:

enum Direction {
    Up = 10,
    Down,
    Left,
    Right
}

console.log(Direction.Up, Direction.Down, Direction.Left, Direction.Right); // 10 11 12 13
1
2
3
4
5
6
7
8

# 字符串枚举

enum Direction {
    Up = 'Up',
    Down = 'Down',
    Left = 'Left',
    Right = 'Right'
}

console.log(Direction['Right'], Direction.Up); // Right Up
1
2
3
4
5
6
7
8

# 异构枚举

enum BooleanLikeHeterogeneousEnum {
    No = 0,
    Yes = "YES",
}
1
2
3
4

# 反向映射

enum Direction {
    Up,
    Down,
    Left,
    Right
}

console.log(Direction.Up === 0); // true
console.log(Direction.Down === 1); // true
console.log(Direction.Left === 2); // true
console.log(Direction.Right === 3); // true
1
2
3
4
5
6
7
8
9
10
11

这就是我们数字枚举那一部分的例子,我们可以通过枚举名字获取枚举值,这当然看起来没问题,那么能不能通过枚举值获取枚举名字呢?

enum Direction {
    Up,
    Down,
    Left,
    Right
}

console.log(Direction[0]); // Up
1
2
3
4
5
6
7
8

这就很奇怪了,我们印象中一个 JavaScript 对象一般都是正向映射的,即 name => value,为什么在枚举中是可以正反向同时映射的?即 name <=> value。

我们往下看,通过了解枚举的本质,我们就可以理解这种正反向同时映射的特性了。

# 枚举的本质

以上面的 Direction 枚举类型为例,我们不妨看一下枚举类型被编译为 JavaScript 后是什么样子:

var Direction;
(function (Direction) {
    Direction[Direction["Up"] = 10] = "Up";
    Direction[Direction["Down"] = 11] = "Down";
    Direction[Direction["Left"] = 12] = "Left";
    Direction[Direction["Right"] = 13] = "Right";
})(Direction || (Direction = {}));
1
2
3
4
5
6
7

这个编译后的代码可能看起来比较复杂,不过我们可以把Direction看成一个对象,比如我们在 TypeScript 中做几个小实验:

enum Direction {
    Up = 10,
    Down,
    Left,
    Right
}

console.log(Direction[10], Direction['Right']); // Up 13
1
2
3
4
5
6
7
8

原因就在编译后的 JavaScript 中体现出来了,因为 Direction[Direction["Up"] = 10] = "Up" 也就是 Direction[10] = "Up" ,所以我们可以把枚举类型看成一个JavaScript对象,而由于其特殊的构造,导致其拥有正反向同时映射的特性。

# 常量枚举

枚举其实可以被 const 声明为常量的,这样有什么好处?我们看以下例子:

const enum Direction {
    Up = 'Up',
    Down = 'Down',
    Left = 'Left',
    Right = 'Right'
}

const a = Direction.Up;
1
2
3
4
5
6
7
8

大家猜一下它被编译为 JavaScript 后是怎样的?

var a = "Up";
1

我们在上面看到枚举类型会被编译为 JavaScript 对象,怎么这里没有了?

这就是常量枚举的作用,因为下面的变量 a 已经使用过了枚举类型,之后就没有用了,也没有必要存在与 JavaScript 中了, TypeScript 在这一步就把 Direction 去掉了,我们直接使用 Direction 的值即可,这是性能提升的一个方案。

如果你非要 TypeScript 保留对象 Direction ,那么可以添加编译选项 --preserveConstEnums

# 联合枚举与枚举成员的类型

我们假设枚举的所有成员都是字面量类型的值,那么枚举的每个成员和枚举值本身都可以作为类型来使用,

  • 任何字符串字面量,如:
const enum Direction {
    Up = 'Up',
    Down = 'Down',
    Left = 'Left',
    Right = 'Right'
}
1
2
3
4
5
6
  • 任何数字字面量,如:
enum Direction {
    Up,
    Down,
    Left,
    Right
}
1
2
3
4
5
6
  • 应用了一元-符号的数字字面量,如:
enum Direction {
    Up = -1,
    Down = -2,
    Left = -3,
    Right = -4,
}
1
2
3
4
5
6

# 枚举成员类型

当所有枚举成员都拥有字面量枚举值时,它就带有了一种特殊的语义,即枚举成员成为了类型。

比如我们声明一个数字类型:

enum Direction {
    Up,
    Down,
    Left,
    Right
}

const a = 0

console.log(a === Direction.Up) // true
1
2
3
4
5
6
7
8
9
10

我们把成员当做值使用,看来是没问题的,因为成员值本身就是0,那么我们再加几行代码:

type c = 0

declare let b: c

b = 1 // 不能将类型“1”分配给类型“0”
b = Direction.Up // ok
1
2
3
4
5
6

我们看到,上面的结果显示这个枚举的成员居然也可以被当做类型使用,这就是枚举成员当做类型使用的情况。

# 联合枚举类型

由于联合联合枚举,类型系统可以知道枚举里的值的集合。

enum Direction {
    Up,
    Down,
    Left,
    Right
}

declare let a: Direction

enum Animal {
    Dog,
    Cat
}

a = Direction.Up // ok
a = Animal.Dog // 不能将类型“Animal.Dog”分配给类型“Direction”
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

我们把 a 声明为 Direction 类型,可以看成我们声明了一个联合类型 Direction.Up | Direction.Down | Direction.Left | Direction.Right,只有这四个类型其中的成员才符合要求。

# 枚举合并

我们可以分开声明枚举,他们会自动合并

enum Direction {
    Up = 'Up',
    Down = 'Down',
    Left = 'Left',
    Right = 'Right'
}

enum Direction {
    Center = 1
}
1
2
3
4
5
6
7
8
9
10

编译为 JavaScript 后的代码如下:

var Direction;
(function (Direction) {
    Direction["Up"] = "Up";
    Direction["Down"] = "Down";
    Direction["Left"] = "Left";
    Direction["Right"] = "Right";
})(Direction || (Direction = {}));
(function (Direction) {
    Direction[Direction["Center"] = 1] = "Center";
})(Direction || (Direction = {}));
1
2
3
4
5
6
7
8
9
10

因此上面的代码并不冲突。

# 为枚举添加静态方法

借助 namespace 命名空间,我们甚至可以给枚举添加静态方法。

我们举个简单的例子,假设有十二个月份:

enum Month {
    January,
    February,
    March,
    April,
    May,
    June,
    July,
    August,
    September,
    October,
    November,
    December,
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14

我们要编写一个静态方法,这个方法可以帮助我们把夏天的月份找出来:

function isSummer(month: Month) {
    switch (month) {
        case Month.June:
        case Month.July:
        case Month.August:
            return true;
        default:
            return false
    }
}
1
2
3
4
5
6
7
8
9
10

想要把两者结合就需要借助命名空间的力量了:

namespace Month {
    export function isSummer(month: Month) {
        switch (month) {
            case Month.June:
            case Month.July:
            case Month.August:
                return true;
            default:
                return false
        }
    }
}

console.log(Month.isSummer(Month.January)) // false
1
2
3
4
5
6
7
8
9
10
11
12
13
14