Typescript 1.8 引入了字符串字面量类型,用于将变量的值限定在有限的字符串字面量集合中。在 Typescript 2.0 中,字面量类型不再局限于字符串字面量。以下的这些字面量类型被添加到了类型系统中:
下面我们就每一种新的字面量类型举一个实际的例子。
下面的代码中定义了两个常量,TRUE
和FALSE
,他们分别保存了 true
和 false
两个值:
const TRUE: true = true; // OK
const FALSE: false = false; // OK
如果你尝试给这两个变量赋值相反的布尔值,会报类型错误:
const TRUE: true = false;
// Error: Type 'false' is not assignable to type 'true'
const FALSE: false = true;
// Error: Type 'true' is not assignable to type 'false'
在引入布尔字面量类型后,预定义的boolean
类型现在等同于 true | false
这个联合类型:
let value: true | false; // Type boolean
布尔字面量类型单独使用可能并没有太多用处,但和标签联合类型(tagged union types)以及基于控制流的类型分析(control flow based types analysis) 配合非常有用。举个例子,Result<T>
泛型要么有一个 T
类型的值,要么包含了一个 string
类型的错误信息,它被定义如下:
type Result<T> =
| { success: true; value: T }
| { success: false; error: string };
这儿有个函数使用了这个泛型:
function parseEmailAddress(
input: string | null | undefined
): Result<string> {
// If the input is null, undefined, or the empty string
// (all of which are falsy values), we return early.
if (!input) {
return {
success: false,
error: "The email address cannot be empty."
};
}
// We're only checking that the input matches the pattern
// <something> @ <something> DOT <something>
// to keep it simple. Properly validating email addresses
// via regex is hard, so let's not even try here.
if (!/^\S+@\S+\.\S+$/.test(input)) {
return {
success: false,
error: "The email address has an invalid format."
};
}
// At this point, control flow based type analysis
// has determined that the input has type string.
// Thus, we can assign input to the value property.
return {
success: true,
value: input
};
}
注意strictNullChecks
配置选项开启后,string
类型是non-nullable
类型。为了使我们的函数能够接收 nullable 类型的参数,null
和 undefined
类型必须显式地添加到联合类型中。
我们现在可以像下面这样调用 parseEmailFunction
函数:
const parsed = parseEmailAddress("example@example.com");
if (parsed.success) {
parsed.value; // OK
parsed.error; // Error
} else {
parsed.value; // Error
parsed.error; // OK
}
下面是以上代码片段在 VSCode 中的截图。注意,有一些属性访问表达式被标注了红色的下划线:
这里面值得一提的是,在我们检查了parsed.success
这个可辨识属性(discriminant property)之后,编译器只允许我们访问 value
或者 error
属性:
parsed.success
是true
,parsed
肯定是{ success: true; value: string }
类型。因此我们可以访问value
属性,但不能访问 error
。parsed.success
是false
,parsed
肯定是{ success: false; error: string }
类型。因此我们可以访问error
属性,但不能访问 value
。顺便提一句,你发现了吗,这段代码中和 Typescript 相关的只有 Result<T>
和函数签名的类型标注?其他的部分都是普通的、原汁原味的 Javascript,而这些代码因为基于控制流的类型分析而得到了类型安全。
和字符串字面量类似,我们可以将一个数字类型变量取值限定于已知的一个有限集合中:
let zeroOrOne: 0 | 1;
zeroOrOne = 0;
// OK
zeroOrOne = 1;
// OK
zeroOrOne = 2;
// Error: Type '2' is not assignable to type '0 | 1'
举个例子,在实际使用中,我们可以用数字字面量类型来定义端口号。HTTP 默认使用 80 端口,HTTPS 默认使用 443 端口。我们可以写一个 getPort
函数,并且在函数签名中声明只能返回这两个可能的值:
function getPort(scheme: "http" | "https"): 80 | 443 {
switch (scheme) {
case "http":
return 80;
case "https":
return 443;
}
}
const httpPort = getPort("http"); // Type 80 | 443
更有趣的是,如果我们结合 Typescript 中的函数重载,会得到更具体的类型:
function getPort(scheme: "http"): 80;
function getPort(scheme: "https"): 443;
function getPort(scheme: "http" | "https"): 80 | 443 {
switch (scheme) {
case "http":
return 80;
case "https":
return 443;
}
}
const httpPort = getPort("http"); // Type 80
const httpsPort = getPort("https"); // Type 443
如下图所示,当我们去比较httpPort
和 443
,编译器会提示我们这个条件语句会永远返回 false
:
既然 httpPort 的类型是 80,那么它永远只能包含 80 的值,而 80 显然永远不会等于 443。类似这样的案例,Typescript 编译器可以帮助你检测有 bugger 的代码和一些不会执行的死代码(dead code)。
最后,我们还能使用枚举字面量类型。继续我们上面的例子,我们将实现一个函数用来映射给定的端口(80 或 443)到相应的协议(HTTP 或 HTTPS)。首先,我们来定义一个枚举常量,包含了这两个端口号:
const enum HttpPort {
Http = 80,
Https = 443
}
现在我们来声明我们的 getScheme
函数,再一次,我们使用函数重载来限定特定类型:
function getScheme(port: HttpPort.Http): "http";
function getScheme(port: HttpPort.Https): "https";
function getScheme(port: HttpPort): "http" | "https" {
switch (port) {
case HttpPort.Http:
return "http";
case HttpPort.Https:
return "https";
}
}
const scheme = getScheme(HttpPort.Http);
枚举常量不会产生运行时的代码(除非你提供了 preserveConstEnums
编译器配置)。也就是说在编译之后,枚举常量的值会被直接内联到代码中。下面就是编译后的 Javascript 代码:
function getScheme(port) {
switch (port) {
case 80:
return "http";
case 443:
return "https";
}
}
var scheme = getScheme(80);
是不是很简洁?