编译命令

通过 Node.js 安装 TypeScript 后,你就可以使用 tsc 编译命令了。在命令行中执行以下命令,分别查看常用 tsc 命令的基本说明及全部说明。

$ tsc --help
$ tsc --help --all

通过以下命令,查看 TypeScript 的版本。

$ tsc --version

受限于篇幅,本节将着重介绍常用参数的用法。

直接编译指定文件

前面使用编译单个文件的方式来输出 JavaScript 文件,命令如下。

$ tsc 文件路径

假设当前的项目目录结构如下。

D:\TSProject
    HelloWorld.ts

D:\TSProject 目录下执行以下 tsc 命令来编译该文件。

$ tsc HelloWorld.ts

该命令会编译 HelloWorld.ts 文件,并在同级目录下生成同名 .js 文件。命令执行后,项目目录的结构如下。

D:\TSProject
    HelloWorld.ts
    HelloWorld.js

编译选项:编译文件及输出路径

--outDir选项

使用 tsc 命令可以同时编译多个文件。例如,执行以下命令将同时编译 a.ts 文件和 b.ts 文件,然后在目录下同时生成 a.js 文件和 b.js 文件。

$ tsc a.ts b.ts

在编译时,通过 --outDir 选项指定文件编译后的输出路径。假设当前的项目结构如下。

D:\TSProject
    a.ts
    b.ts

D:\TSProject 目录下执行以下命令,将编译后的文件输出到 output 子目录中。

$ tsc a.ts b.ts --outDir output

该命令会编译 HelloWorld.ts 文件,并在同级目录下生成同名 .js 文件。命令执行后,项目结构如下。

D:\TSProject
│  a.ts
│  b.ts
│
└─output
        a.js
        b.js

--outFile选项

通过 --outFile 选项指定输出文件的路径及名称。如果同时编译多个文件,则所有代码将合并到同一个文件当中。假设当前的项目结构如下。

D:\TSProject
    a.ts
    b.ts

a.ts 文件的代码如下。

let a=1;

b.ts 文件的代码如下。

let b=true;

D:\TSProject 目录下执行以下命令,将 a.ts 文件及 b.ts 文件的内容编译到 c.js 文件中。

$ tsc a.ts b.ts --outDir c.js

命令执行后,项目结构如下。

D:\TSProject
    a.ts
    b.ts
    c.js

c.js 文件的代码如下。

var a = 1;
var b = true;

编译选项:按需输出JavaScript代码

根据运行平台和需求,你可以将 TypeScript 代码编译为不同的 JavaScript 代码。本节将介绍常用的编译选项。

--target选项

不同平台对 ECMAScript 版本的支持情况也不相同,通过 --target 选项(选项缩写 -t)指明编译成哪个 ECMAScript 版本的 JavaScript 代码。

--target 选项的默认值为 es3,可设置的值有 es3es5es6es2015es2016es2017es2018es2019es2020es2021esnext。在这些值中,es6 等同于 es2015esnext 表示最新的 ECMAScript 版本。

假设 a.ts 文件的代码如下。

let a: () => void = () => { console.log("hello") };

如果执行 tsc a.ts(等同于执行 tsc a.ts --target es3 )命令,编译后输出的 a.js 文件如下。

var a = function () { console.log("hello"); };

这是因为 let 声明和箭头函数是从 ECMAScript 6 发布之后才开始支持的新特性,按照 ECMAScript 3 编译,将会输出兼容 ECMAScript 3JavaScript 代码。

如果执行 tsc a.ts --target es6 命令,按照 ECMAScript 6 编译并输出文件,则 a.js 文件将会保留 let 声明和箭头函数,编译后输出的 a.js 文件如下。

let a = () => { console.log("hello") };

--module选项

不同平台对模块的支持情况各不相同,例如,Node.js 不支持 AMD 模块,而浏览器不支持 CommonJS 模块。通过 --module 选项(或使用缩写选项 -m)指明将 TypeScript 中涉及模块的代码按哪种模块规范编译成 JavaScript 代码。

--module 选项的默认值将根据 --target 选项的取值而定,当 --target 选项的值为 es3es5 时,--module 选项的默认值为 commonjs,在其他情况下 --module 选项的默认值为 es6(等同于 es2015)。该选项全部可设置的值有 nonecommonjsamdsystemumdes6es2015es2020es2022esnextnode12nodenext

关于 --module 选项与输出,可参考13.1.5节,这里不再赘述。

--removeComments选项

默认情况下,编译后的 JavaScript 代码将保留前面在 TypeScript 代码中的注释。在命令中加入选项后,将会在编译时移除所有的注释代码。

假设 a.ts 文件的代码如下。

//This file is a.ts
/*This file is a.ts */
let a = 1;

执行 tsc a.ts --removeComments 命令并编译后,输出的 a.js 文件如下。

var a = 1;

编译选项:具有调试作用的选项

除在发布项目前需要将 TypeScript 编译成 JavaScript 代码之外,在编码阶段也会频繁地编译并调试代码。编译命令中的部分编译选项可用于调试代码。

  • --noEmit 选项 --noEmit 选项用于编译,但不会输出 JavaScript 文件,该选项通常用于检查是否存在编译错误;或者使用第三方工具将 TypeScript 文件转换为 JavaScript 文件。

  • --watch 选项 编译通常是手动触发的,但可以使用 --watch 选项(或使用缩写选项 -w)进入观察模式。当修改被观察的 TypeScript 文件时,将会自动触发编译。

例如,执行 tsc a.ts --watch 命令后,输出如下,这表示已进入观察模式并进行编译,观察的文件是 a.ts 文件,同时编译并输出了 a.js 文件。

> [12:52:05 PM] Starting compilation in watch mode...
> [12:52:07 PM] Found 0 errors. Watching for file changes.

修改 a.ts 文件的代码并保存,编译器会重新编译并输出 a.js 文件,并将编译情况输出到命令行中,如下所示。

> [12:53:27 PM] File change detected. Starting incremental compilation...
> [12:53:27 PM] Found 0 errors. Watching for file changes

观察模式将持续进行,对 a.ts 文件的任何修改都将触发自动编译,直到手动中断该命令的执行为止。

编译选项:类型检查

TypeScript 中提供了一些用于类型检查的编译选项,其中使用最多的是与严格类型检查相关的选项。

--strict选项

--strict 选项用于启用一系列严格类型检查选项。打开它相当于启用了所有严格类型检查选项。这些选项如下。

  • --alwaysStrict

  • --strictNullChecks

  • --strictBindCallApply

  • --strictFunctionTypes

  • --strictPropertyInitialization

  • --noImplicitAny

  • --noImplicitThis

  • --useUnknownInCatchVariables

如果开启了 --strict 选项,可以根据需要关闭单个检查选项。

--alwaysStrict选项

--alwaysStrict 选项用于按照 ECMAScript 定义的严格模式来编译文件,并为每个编译后的 .js 文件增加一行 "use strict" 代码。

假设 a.ts 文件的代码如下。

let a = 1;

使用 tsc --alwaysStrict 选项编译文件,编译后的 a.js 文件如下,增加的一行 "use strict" 代码,表示代码将运行于严格模式下。

"use strict";
var a = 1;

使用 tsc --alwaysStrict 选项编译 .ts 文件,编译器将检查文件代码是否符合 ECMAScript 定义的严格模式。如果不符合,将引起编译错误。例如,以下 .ts 文件将出现编译错误,因为在严格模式下不允许使用八进制字面量。

//错误TS1121: 在严格模式下不允许使用八进制字面量
let x = 010;

--strictNullChecks选项

--strictNullChecks 选项主要用于检查在使用某个对象的属性或方法时,该对象是否可能为 nullundefined,如果有这种可能,则会引起编译错误。

例如,在以下代码中,由于 age 对象是可选属性,在 printUserInfo() 函数中对 age 对象使用 toString() 方法时,其值可能为 nullundefined,因此当开启 --strictNullChecks 选项后将引起编译错误。

interface User {
    name: string;
    age?: number;
}
function printUserInfo(user: User) {
    //错误TS2532: user.age可能值为'undefined'
    console.log(`I am ${user.name}, I am ${user.age.toString()} old.`)
}

--strictBindCallApply选项

--strictBindCallApply 选项开启后,在调用函数的内置方法 call()bind()apply() 时,如果传入参数的类型与声明函数时的参数类型不匹配,将引起编译错误。

例如,在以下代码中,在声明 printString() 函数时,参数类型为 string,而调用 printString() 函数的 call() 方法时,传入的参数值为 falsefalse 为布尔类型,与声明时的 string 类型不匹配,因此将引起编译错误。

function printString(str: string) {
    return console.log(str);
  }

printString.call(this, "x");

//错误TS2345: boolean类型的值不可分配给string类型的参数
printString.call(this, false);

--strictFunctionTypes选项

--strictFunctionTypes 选项开启后,将会对函数的参数进行严格检查。如果函数的参数类型不兼容,则会引起编译错误。

例如,以下代码首先声明了 sayHello() 函数,其参数为 string 类型,接着声明了类型别名 StringOrNumberFunc,其类型为一个参数为 stringnumber 的函数,最后将 sayHello() 函数赋给类型为 StringOrNumberFunc 的变量 func,由于参数类型不匹配,因此将引起编译错误。

function sayHello(name: string) {
    console.log("Hello, " + name);
}
type StringOrNumberFunc = (numOrStr: string | number) => void;
//错误TS2322:不能将类型"(name: string) => void"分配给类型"StringOrNumberFunc"。参数
//"name"和"numOrStr" 的类型不兼容
let func: StringOrNumberFunc = sayHello;
func(10);

--strictPropertyInitialization选项

--strictFunctionTypes 选项开启后,TypeScript 编译器将会检查是否为类的各个属性设置过默认值,如果未设置过默认值,将引起编译错误。

例如,在以下代码中,由于 age 属性无初始值,因此将引起编译错误。

class User {
    name: string;
    userType = "user";
    //phoneNumber不会引起编译错误,因为显式声明中的类型包含undefined,所以允许默认值为undefined
    phoneNumber: string | undefined;
    //错误TS2564: "age"属性既没有设定初始值,也没在构造函数中赋值
    age: number;
    constructor(name: string) {
        this.name = name;
    }
}

--noImplicitAny选项

--noImplicitAny 选项开启后,TypeScript 编译器将不允许代码中的任何参数隐式推断为 any 类型,要使用 any 类型必须以 “参数名称:any” 的方式显式声明,否则将引起编译错误。

例如,在以下代码中,printArrayLength() 函数中有一个名为 array 的参数,由于未指明它的类型,因此编译器将尝试推断它的类型。当无法得出确切结果时,则会认为它是 any 类型的参数。一旦开启了 --noImplicitAny 选项,就会引起编译错误。

function printArrayLength(array) {
    //错误TS7006: 参数"array"隐式具有"any"类型
    console.log(array.length);
}
printArrayLength(42);

--noImplicitThis选项

--noImplicitThis 选项开启后,TypeScript 编译器将不允许代码中的 this 对象隐式推断为 any 类型,否则将引起编译错误。

例如,以下代码声明了 selfIntroduction() 函数,函数体中使用了 this.name 属性,虽然最后将 selfIntroduction() 函数的值赋给 user 对象的 selfIntroduction() 方法,但由于 selfIntroduction() 函数并不是在对象中声明的,可以在任意位置引用,因此 this 对象是不确定的,依然会被隐式推断为 any 类型,最终引起编译错误。

function selfIntroduction() {
    //"this"隐式具有"any"类型,因为它没有类型声明
    console.log(`My name is ${this.name}`);
}

let user = {
    name: "harry",
    selfIntroduction: selfIntroduction
}

user.selfIntroduction();

--useUnknownInCatchVariables选项

--useUnknownInCatchVariables 选项开启后,catch 语句中的参数将变为 unknown 类型,而非 any 类型,这意味着必须通过类型断言或类型防护等方式使用 unknown 类型,一旦像使用 any 类型那样直接使用某个属性或方法,就会引起编译错误。

例如,在以下代码中,将 err 参数作为 any 类型来使用,将引起编译错误。

try {
    //...
}
catch (err) {
    //错误TS2571: 对象的类型为'unknown'
    console.log(err.message);
}

有两种方式可以避免此类编译错误。第一种方式是将 catch 语句中的参数显式声明为 any 类型,示例代码如下。

try {
    //...
}
catch (err: any) {
    console.log(err.message);
}

第二种方式是使用类型断言或类型防护语句,先让类型变得明确,然后再使用具体属性或方法。

try {
    //...
}
catch (err: any) {
    if (err instanceof Error) {
        console.error(err.message);
    }
}