编译命令
通过 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,可设置的值有 es3、es5、es6、es2015、es2016、es2017、es2018、es2019、es2020、es2021、esnext。在这些值中,es6 等同于 es2015,esnext 表示最新的 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 3 的 JavaScript 代码。
如果执行 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 选项的值为 es3 或 es5 时,--module 选项的默认值为 commonjs,在其他情况下 --module 选项的默认值为 es6(等同于 es2015)。该选项全部可设置的值有 none、commonjs、amd、system、umd、es6、es2015、es2020、es2022、esnext、node12、nodenext。
关于 --module 选项与输出,可参考13.1.5节,这里不再赘述。
编译选项:具有调试作用的选项
除在发布项目前需要将 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 选项主要用于检查在使用某个对象的属性或方法时,该对象是否可能为 null 或 undefined,如果有这种可能,则会引起编译错误。
例如,在以下代码中,由于 age 对象是可选属性,在 printUserInfo() 函数中对 age 对象使用 toString() 方法时,其值可能为 null 或 undefined,因此当开启 --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() 方法时,传入的参数值为 false,false 为布尔类型,与声明时的 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,其类型为一个参数为 string 或 number 的函数,最后将 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);
}
}