
显式类型
在前面的例子中,我们没有指定 person 和 date 的类型。现在,我们来修改代码,明确告知 TypeScript:person 是一个字符串,date 是一个 Date 对象。同时,我们还将调用 date 的 toDateString() 方法。
function greet(person: string, date: Date) {
console.log(`Hello ${person}, today is ${date.toDateString()}!`);
}
我们在 person 和 date 参数后面添加了类型注解,用于描述调用 greet 时可以传入的值类型。你可以这样解读函数签名:greet 接收一个 string 类型的 person 和一个 Date 类型的 date。
添加类型注解后,TypeScript 就能帮助我们发现对 greet 函数的错误调用。例如:
function greet(person: string, date: Date) {
console.log(`Hello ${person}, today is ${date.toDateString()}!`);
}
greet("Maddison", Date());
// 错误:类型“string”的参数不能赋给类型“Date”的参数。
等等,为什么第二个参数会报错?
这可能会让不少人感到意外:在 JavaScript 中,直接调用 Date()(不加 new)返回的是一个字符串。而我们期望的是一个 Date 对象,因此需要使用 new Date() 来构造:
greet("Maddison", new Date());
修复后的代码如下:
function greet(person: string, date: Date) {
console.log(`Hello ${person}, today is ${date.toDateString()}!`);
}
greet("Maddison", new Date());
需要强调的是,我们并不总是需要显式地编写类型注解。在许多情况下,即使省略类型,TypeScript 也可以自动推断出变量的类型。
在上面的图片示例中,尽管我们没有告诉 TypeScript msg 是 string 类型,它依然成功地推断了出来。这恰好是 TypeScript 的一项优势——如果类型系统能够自动推断,通常就不需要手动添加注解。
[!note]
上图中的提示气泡模拟了在编辑器中将鼠标悬停在变量上时,TypeScript 提供的类型推断信息。
类型擦除
让我们看看用 tsc 编译上述 greet 函数后,会生成怎样的 JavaScript 代码:
"use strict";
function greet(person, date) {
console.log("Hello ".concat(person, ", today is ").concat(date.toDateString(), "!"));
}
greet("Maddison", new Date());
请注意两个关键点:
person 和 date 参数上的类型注解完全消失了。
- 使用反引号编写的模板字符串被转换成了普通的字符串拼接。
第二点我们稍后讨论,先聚焦第一点:类型注解在输出的 JavaScript 中被彻底移除了。因为类型注解并非 JavaScript(更准确地说,是 ECMAScript)的语法,所以没有任何浏览器或运行时能够直接执行未处理的 TypeScript 代码。
这也正是 TypeScript 需要编译器的根本原因——它必须移除或转换所有 TypeScript 特有的语法,生成标准的 JavaScript 代码才能运行。大多数 TypeScript 语法,包括类型注解,都会在编译阶段被“擦除”。
[!note]
请记住:类型注解不会改变程序的运行时行为。
降级编译
在上面的编译结果中,另一个明显的变化是模板字符串被重写了。原始代码是:
`Hello ${person}, today is ${date.toDateString()}!`;
编译输出变成了:
"Hello ".concat(person, ", today is ").concat(date.toDateString(), "!");
为什么会这样?
这是因为模板字符串是 ECMAScript 2015(也称为 ES6)引入的新特性。TypeScript 能够将较新版本 ECMAScript 的代码“降级”编译为较旧的版本,例如 ES3 或 ES5。这个过程被称为降级编译。
TypeScript 默认的编译目标是 ES5,这是一个相对早期的 ECMAScript 版本。如果我们希望生成更现代的代码,可以通过设置 target 编译选项来选择更高的目标版本。例如,运行以下命令:
tsc --target es2015 hello.ts
这将把编译目标设置为 ECMAScript 2015,意味着生成的代码会保留模板字符串等 ES2015 特性(假设运行环境支持)。运行上述命令后,输出如下:
function greet(person, date) {
console.log(`Hello ${person}, today is ${date.toDateString()}!`);
}
greet("Maddison", new Date());
[!note]
虽然默认目标是 ES5,但目前绝大多数浏览器都已支持 ES2015。因此,除非你需要兼容非常老旧的浏览器,大多数开发者都可以安全地将编译目标设置为 ES2015 或更高版本。
严格模式
不同的开发者对 TypeScript 类型检查的严格程度有不同的需求。
有些人更喜欢一种宽松、可选的方式,只对程序的部分区域进行类型验证,同时享受良好的工具支持。这也是 TypeScript 的默认体验——类型是可选的,推断尽可能宽松,并且不会对可能为 null 或 undefined 的值进行严格检查。就像 tsc 报错时仍然会生成输出一样,这种默认设置旨在“尽量不打扰你”。对于迁移现有的 JavaScript 项目,这种宽松的方式是一个很好的起点。
相反,许多开发者希望 TypeScript 尽可能严格地验证代码。为此,TypeScript 提供了一系列严格模式配置选项。
这些配置项将静态类型检查从一个简单的“开关”转变为一个可调节的“刻度盘”。你“拧得越紧”,TypeScript 检查得就越严格。虽然这可能会增加初期的工作量,但从长远来看通常是值得的,它能发现更多潜在问题,并提供更强大的工具支持。对于新项目,强烈建议启用所有严格模式选项。
TypeScript 提供了多个可以单独开启或关闭的类型检查严格性标志。除非特别说明,本手册中的所有示例都将在启用所有严格模式的前提下进行。你可以通过命令行参数 --strict,或在 tsconfig.json 中配置来一次性启用所有选项:
{
"compilerOptions": {
"strict": true
}
}
当然,你也可以根据需求单独关闭某些检查。
其中最重要的两个严格选项是:
noImplicitAny:禁止隐式的 any 类型
strictNullChecks:严格的空值检查(控制 null 和 undefined)
noImplicitAny
如前所述,在某些情况下,TypeScript 不会主动推断具体类型,而是回退到最宽松的类型:any。这本身并不一定是坏事——使用 any 本质上就是回归到原生 JavaScript 的开发体验。
但问题在于,频繁使用 any 会削弱 TypeScript 的价值。你的程序类型信息越完整,类型系统提供的验证和工具支持就越强大,编码过程中遇到的错误也就越少。
启用 noImplicitAny 编译选项后,TypeScript 会对所有被隐式推断为 any 的变量抛出错误,帮助你及早发现类型缺失的问题。
strictNullChecks
默认情况下,null 和 undefined 可以被赋值给任何其他类型。这在某些场景下让编码更便捷,但忘记处理 null 和 undefined 是现实世界中无数 Bug 的根源——甚至被称为“价值十亿美元的错误”。
启用 strictNullChecks 后,TypeScript 会要求你显式地处理 null 和 undefined,从类型层面强制你考虑这些情况,从而避免遗漏相关判断,减少由空值引发的潜在问题。
译者解读
本篇是 TypeScript 官方手册《基础知识》的下半部分内容。
下半部分继续介绍了 TypeScript 的几个核心基本特征:
- 类型注解:如何显式地为变量和参数指定类型。
- 类型擦除:编译后类型信息会被移除,不影响运行时。
- 降级编译:可以灵活选择编译生成的 JavaScript 目标版本。
- 严格模式:通过配置开启更严格的类型检查,提升代码质量。
其中有几点值得特别注意:
- 按照官方的建议,实际上不推荐过度使用显式类型注解,更推荐充分利用 TypeScript 强大的类型推断能力。
- 如果是全新的项目,建议直接在配置中设置
"strict": true 来启用所有严格模式选项。尽管在编写代码时可能会感觉约束更多,但这能极大地提升代码的健壮性和可维护性。这部分关于工程化配置的知识,可以在前端框架/工程化板块找到更多深入讨论。
- 如果是对一个已有的 JavaScript 代码库升级为 TypeScript,那么可以采用渐进式策略,逐步开启严格模式的各个开关。
希望这篇对 TypeScript 官方手册的解读能帮助你更好地理解这门语言的基础。如果你想了解更多关于 JavaScript 和 TypeScript 的核心概念,例如异步编程或事件循环,可以访问云栈社区的 HTML/CSS/JS 板块获取更多学习资源。