编译命令
通过 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);
}
}