本文是 Typescript Evolution 系列的第 2 篇,原作者已经更新到了40多篇。该系列从 Typescript 2.0 开始,对每次语言更新新增的重要特性都做了通俗易懂的介绍,等不及翻译的同学建议直接阅读系列原文。
Typescript 2.0 发布带来了许多新的特性。我前面写过一篇文章介绍 non-nullable 类型,和本文中要讨论的内容是相关的,并且可以配合使用,它就是:基于控制流的类型分析。
官方的 What‘s new in TypeScript 对基于控制流的类型分析总结如下:
Typescript 2.0 中,类型检查器会分析语句和表达式所有可能的控制流,将声明为联合类型的局部变量或参数收窄到尽可能具体的类型(类型收窄)。
上面的解释相当滞重,不好理解。这儿有个例子可以说明 Typescript 如何理解局部变量赋值语句的效果,并且相应地收窄变量的类型:
let command: string | string[];
command = "pwd";
command.toLowerCase(); // Here, command is of type 'string'
command = ["ls", "-la"];
command.join(" "); // Here, command is of type 'string[]'
注意,所有的代码都在同一个作用域内。可以看到,类型检查器对任意位置的 command
变量都使用了尽可能具体的类型:
command
变量不可能是字符串数组(联合类型中的另一种可能)。因此,Typescript 把 command
当作 string 类型的变量,允许调用 toLowerCase()
方法。["ls", "-la"]
字符串数组后,command
变量不再被当作字符串。它已经是字符串数组,所以可以成功调用 join
方法。因为相同的控制流分析,下面的函数在 Typescript 2.0 中是类型安全的:
function composeCommand(
command: string | string[]
): string {
if (typeof command === "string") {
return command;
}
return command.join(" ");
}
编译器现在能够理解如果 command
参数是 string 类型,那么函数会在 if 语句中提早返回。因为这个提早返回的行为,if 语句之后,command
参数的类型被收窄为 string[] 。结果就是,调用 join
方法是类型安全的。
但在 Typecript 2.0 之前,编译器不能够理解上面代码的语义。因为 sting 类型不会从 command
变量的联合类型中移除,所以会产生下面的编译时错误:
property 'join' does not exist on type 'string | string[]'.
基于控制流的类型分析和 nullable 类型配合的时候尤其有用。nullable 类型表示可以为 null 或 undefined 的联合类型。通常在使用一个 nullable 类型的变量之前,我们需要检查它是否是一个非空的值:
type Person = {
firstName: string;
lastName?: string | null | undefined;
};
function getFullName(person: Person): string {
const { firstName, lastName } = person;
// Here, we check for a falsy value of the `lastName` property,
// which covers `null` and `undefined` (and other values like "")
if (!lastName) {
return firstName;
}
return `${firstName} ${lastName}`;
}
这里,Person
类型定义了一个 non-nullable 属性 firseName 以及一个 nullable 属性 lastName。如果我们需要组合两者生成全名,我们需要检查 lastName 是否是 null 或者 undefined 来避免将 “null” 或者 “undefined” 添加到 firstName 后面。
为了方便说明,我给 lastName 属性的联合类型添加了 undefined 类型,虽然这样做其实是多余的。在严格 null 检查模式下,undefined 类型会自动添加到可选属性的联合类型中,所以我们不需要显式地写出来。如果想了解更多,可以看我之前写的文章non-nulable 类型。
另一个基于控制流的新特性叫做明确赋值分析(definite assignment analysis)。在严格 null 检查模式下,局部变量在它们被赋值之前不能够被使用:
let name: string;
// Error: Variable 'name' is used before being assigned.
console.log(name);
这条规则的一个例外是那些包含 undefined 类型的局部变量:
let name: string | undefined;
console.log(name); // No error
明确赋值分析是又一种避免因空值导致 bug 的保护机制。它背后的思考是,确保所有 non-nullble 类型的局部变量在使用之前都被适当地初始化了。
基于控制流的类型分析是对 Typescript 类型系统强有力的补充。类型检查器现在可以理解赋值语句以及控制流跳转的语义,因此极大地减少了类型保护的需要。通过排除 null 和 undefined 类型能够更方便地使用 nullable 类型的变量。最后,基于控制流的分析可以阻止变量在明确赋值之前被使用。