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:

npm install -g typescript

编写tsc配置文件jsconfig.json

{
    "compilerOptions": {
        "target": "ES6",
        "module": "commonjs",
        "sourceMap": true,
        "rootDir": "src",
        "outDir": "dist"
    },
    "include": [
        "src/**/*"
    ],
    "exclude": [
        "dist",
        "node_modules"
    ]
}

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

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

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的使用需要注意,一旦申明之后,后续不可将整个变量替换:

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

则不可再

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

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

a.x = 3; // ok

3.3 接口

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

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

只读类型:

  • 属性名之前添加readonly
  • ReadonlyArray

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

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

函数接口:

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

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

3.5 函数

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

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

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

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

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

class Handler {
    info: string;
    onClickBad(this: Handler, e: Event) {
        // oops, used this here. using this callback would crash at runtime
        this.info = e.message;
    };
}
let h = new Handler();
uiElement.addClickListener(h.onClickBad); // error!

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

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

class Handler {
    info: string;
    onClickGood(this: void, e: Event) {
        // can't use this here because it's of type void!
        console.log('clicked!');
    }
}
let h = new Handler();
uiElement.addClickListener(h.onClickGood);
// OR
class Handler {
    info: string;
    onClickGood = (e: Event) => { this.info = e.message }
}

3.10 高级类型

联合类型:

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

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

interface Bird {
    fly();
    layEggs();
}

interface Fish {
    swim();
    layEggs();
}

function getSmallPet(): Fish | Bird {
    // ...
}

let pet = getSmallPet();
pet.layEggs(); // okay
pet.swim();    // errors

// 断言 e.g:
if ((<Fish>pet).swim) {
    (<Fish>pet).swim();
} else {
    (<Bird>pet).fly();
}

function isFish(pet: Fish | Bird): pet is Fish {
    return (<Fish>pet).swim !== undefined;
}

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

type Name = string;
type NameResolver = () => string;
type NameOrResolver = Name | NameResolver;
function getName(n: NameOrResolver): Name {
    if (typeof n === 'string') {
        return n;
    }
    else {
        return n();
    }
}

// 类型别名不能出现在声明右侧的任何地方。
type Yikes = Array<Yikes>; // error

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

3.13 模块

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

// declarations.d.ts
declare module "hot-new-module";

// 简写模块里所有导出的类型将是 any
import x, {y} from "hot-new-module";
x(y);