0. 前言

TypeScript现在基本上是JS强类型超集语言的事实标准了,作为一个JS工作者,学习也是必须的事情。虽然之前因为使用上的便利性以及语言性能的问题,我一直都没有真的把TS放在心上,这次算是为了自己的职业突破,开始着手进行学习和准备。

而VsCode是微软开始拥抱开源以来的一个非常不错的实践范例,作为TS同一家公司的产品,支持当然非常好。所以这次也是一并尝试。

1. 资源

2. TypeScript上手使用

TS是JS的超集,兼容JS,所以使用上问题不会太大。但有一个问题需要预先解决,就是TS是无法在Node和浏览器内直接运行的,需要转译成JS才能运行。

2.1 转译Node代码

转译成Node需要:

  • 全局安装typescript(附带了tsc命令行工具)
  • 命令行下使用tsc转译JS代码

安装TypeScript:

1 npm install -g typescript

编写tsc配置文件jsconfig.json

 1 {
 2     "compilerOptions": {
 3         "target": "ES6",
 4         "module": "commonjs",
 5         "sourceMap": true,
 6         "rootDir": "src",
 7         "outDir": "dist"
 8     },
 9     "include": [
10         "src/**/*"
11     ],
12     "exclude": [
13         "dist",
14         "node_modules"
15     ]
16 }

这里有个坑:outDir还有rootDir都是放在compilerOptions这个节点下面的,而不是放在根目录下的。这个范例居然很少,我一直都没找到,试了好久才试出来,不得不说文档实在是太差。

命令行下运行tsc进行转译:

1 tsc -p jsconfig.json --watch

这样,代码就能在node里简单运行了。

2.2 转译WEB代码

3. TypeScript语法

3.1 类型

  • 任意类型为any
  • 空类型为void--strictNullChecks开关可以在转译的时候进行空检查
  • 多种类型的可能性的混合,联合类型
  • never
    • 是任何类型的子类型,也可以赋值给任何类型
    • 但不可接受任何非never的类型
    • 返回never的函数必须存在无法达到的终点
  • 类型推断:
    • someValue
    • someValue as string
    • 两者等价,推荐使用as

就这几个需要注意下。

3.2 变量申明

const的使用需要注意,一旦申明之后,后续不可将整个变量替换:

1 const a = {"x": 1}; // defined

则不可再

1 a = {"x": 2}; // failed

但可以单独对对象内部进行改动:

1 a.x = 3; // ok

3.3 接口

接口在TS里就是类型申明,可以理解为Golang中的struct或者C语言中的struct。仅仅只是对一种类型的数据结构进行命名,方便后续的使用。

此外,类型的申明中可以包含可选属性,该属性可以存在,也可以不存在。申明时,使用名字后面加问号的方式:color?

只读类型:

  • 属性名之前添加readonly
  • ReadonlyArray

最简单判断该用readonly还是const的方法是看要把它做为变量使用还是做为一个属性。 做为变量使用的话用const,若做为属性则使用readonly

索引签名: [propName: string]: any;:带有任意数量的其它属性

函数接口:

1 interface SearchFunc {
2   (source: string, subString: string): boolean; // (参数): 返回值
3 }

接口也可以由class使用implements关键字来实现,这和其他语言基本没区别。 接口可以使用extends关键字相互扩展。

3.5 函数

tsc的--noImplicitThis可以协助你找出有问题的this

但在启用该选项的同时,tsc会对this的类型进行检查,所以在编写代码的时候需要小心处理。可以使用显式申明的方式来告知tsc,这里的this是什么类型的,或者应不应该进行检查。

修改的方法是,提供一个显式的this参数。 this参数是个假的参数,它出现在参数列表的最前面:

1 function f(this: void) {
2     // make sure `this` is unusable in this standalone function
3 }
4 // e.g:
5 interface UIElement {
6     addClickListener(onclick: (this: void, e: Event) => void): void;
7 }

当然this申明也是可以带参数的:

1 class Handler {
2     info: string;
3     onClickBad(this: Handler, e: Event) {
4         // oops, used this here. using this callback would crash at runtime
5         this.info = e.message;
6     };
7 }
8 let h = new Handler();
9 uiElement.addClickListener(h.onClickBad); // error!

但这个范例会报错,因为之前我们要求UIElement的回调是void类型的this,而这个下面的范例给的是一个Handler类型的this,类型检测就报错了。

这里可以将申明改为void的this,但同时也无法在函数内使用this。或者使用箭头函数,但每个Handler对象都会创建自己的箭头函数闭包。e.g:

 1 class Handler {
 2     info: string;
 3     onClickGood(this: void, e: Event) {
 4         // can't use this here because it's of type void!
 5         console.log('clicked!');
 6     }
 7 }
 8 let h = new Handler();
 9 uiElement.addClickListener(h.onClickGood);
10 // OR
11 class Handler {
12     info: string;
13     onClickGood = (e: Event) => { this.info = e.message }
14 }

3.10 高级类型

联合类型:

1 /**
2  * Takes a string and adds "padding" to the left.
3  * If 'padding' is a string, then 'padding' is appended to the left side.
4  * If 'padding' is a number, then that number of spaces is added to the left side.
5  */
6 function padLeft(value: string, padding: string | number) {
7     // ...
8 }

遇到联合类型时候的类型断言:

 1 interface Bird {
 2     fly();
 3     layEggs();
 4 }
 5 
 6 interface Fish {
 7     swim();
 8     layEggs();
 9 }
10 
11 function getSmallPet(): Fish | Bird {
12     // ...
13 }
14 
15 let pet = getSmallPet();
16 pet.layEggs(); // okay
17 pet.swim();    // errors
18 
19 // 断言 e.g:
20 if ((<Fish>pet).swim) {
21     (<Fish>pet).swim();
22 } else {
23     (<Bird>pet).fly();
24 }
25 
26 function isFish(pet: Fish | Bird): pet is Fish {
27     return (<Fish>pet).swim !== undefined;
28 }

类型别名,以type关键字创建一个新的类型名:

 1 type Name = string;
 2 type NameResolver = () => string;
 3 type NameOrResolver = Name | NameResolver;
 4 function getName(n: NameOrResolver): Name {
 5     if (typeof n === 'string') {
 6         return n;
 7     }
 8     else {
 9         return n();
10     }
11 }
12 
13 // 类型别名不能出现在声明右侧的任何地方。
14 type Yikes = Array<Yikes>; // error

另一个重要区别是类型别名不能被extends和implements。

3.13 模块

在TypeScript中,假如你不想在使用一个新模块之前花时间去编写声明,你可以采用声明的简写形式以便能够快速使用它。

1 // declarations.d.ts
2 declare module "hot-new-module";
3 
4 // 简写模块里所有导出的类型将是 any
5 import x, {y} from "hot-new-module";
6 x(y);