【TypeScript 演化史 -- 4】更多的字面量类型 与 内置类型声明
husky-dot opened this issue · 0 comments
作者:Marius Schulz
译者:前端小智
来源:Marius Schulz
阿里云最近在做活动,低至2折,真心觉得很划算了,可以点击本条内容或者链接进行参与:
https://promotion.aliyun.com/ntms/yunparter/invite.html?userCode=pxuujn3r
腾讯云最近在做活动,百款云产品低至 1 折,可以点击本条内容或者链接进行参与
为了保证的可读性,本文采用意译而非直译。
TypeScript 1.8 引入了字符串字面量类型,用于将变量限制为可能的字符串值的有限集。在 TypeScript 2.0 中,字面量类型不再局限于字符串。以下字面量类型已添加到类型系统中:
- boolean 字面量类型
- 数字字面量类型
- 枚举字面量类型
接下来,来看看这个类型对应的一些事例。
boolean 字面量类型
下面的示例定义了两个常量 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 字面类型的引入,预定义的 boolean 类型现在等价于 true | false
的联合类型:
let value: true | false; // Type boolean
虽然 boolean 字面量类型在隔离时很少有用,但它们与标记联合类型和基于控制流的类型分析结合使用时非常有效。例如,可以定义一个泛型 Result <T>
类型,该类型要么包含一个类型为 T
的值,要么包含一个类型为 string
的错误消息,如下所示
type Result<T> =
| { success: true; value: T }
| { success: false; error: string };
这是一个接受参数的函数:
function parseEmailAddress(
input: string | null | undefined
): Result<string> {
// 如果 input 为 null,undefined 或空字符串
//(所有都是虚假的值),就直接返回。
if (!input) {
return {
success: false,
error: "The email address cannot be empty."
};
}
// 我们只检查 input 是否与模式匹配
// <something> @ <something> . <something>
// 保持简单,正确验证电子邮件地址
if (!/^\S+@\S+\.\S+$/.test(input)) {
return {
success: false,
error: "The email address has an invalid format."
};
}
return {
success: true,
value: input
};
}
请注意,启用 strictNullChecks
选项后,string
是不可为 null
的类型。为了使函数的 input
参数接受可为 null
的类型的值,必须在联合类型中明确包含 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
}
请注意,某些属性访问表达式用红色波浪线下划线:
这样做的好处是,编译器仅在检查了 parsed.success
后才允许咱们使用value
或error
属性:
-
如果
parsed.success
为true
,则parsed
的类型必须为{ success: true; value: string }
。在这种情况下,咱们可以访问value
,但不能访问error
。 -
如果
parsed.success
为false
,则parsed
的类型必须为{ success: false; error: string }
。在这种情况下,咱们可以访问error
,但不能访问value
。
数字字面量类型
与字符串字面量类型类似,我们可以将数值变量限制为已知值的有限集
let zeroOrOne: 0 | 1;
zeroOrOne = 0;
// OK
zeroOrOne = 1;
// OK
zeroOrOne = 2;
// 错误:类型 '2' 不能分配给类型 '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 的函数重载结合起来,那就更有趣了。这样,我们可以为 getPort
函数的不同重载提供更具体的类型。
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
进行比较时:
由于 httpPort
的类型为 80
,因此它始终包含值 80
,该值当然永远不会等于值 443
。在这种情况下,TypeScript 编译器可以帮助咱们检测错误的逻辑和无效的代码。
枚举字面量类型
最后,咱们还可以使用枚举作为字面量类型。继续前面的示例,实现一个给定端口(80
或443
)映射到相应方案(分别为 HTTP
或 HTTPS
)的函数。为此,我们首先声明一个const enum
,它对两个端口号进行构建:
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);
// Type "http"
常量枚举没有运行时表现形式(除非提供了preserveConstEnums
编译器选项),也就是说,enum
用例的常量值将被内联到使用它们的任何地方。下面是经过编译的 JS 代码,去掉了注解:
function getScheme(port) {
switch (port) {
case 80:
return "http";
case 443:
return "https";
}
}
var scheme = getScheme(80);
是不是超级简洁?
TypeScript 2.0 让咱们以更细粒度地控制项目中包含哪些内置 API 声明。以前,只有在的项目配置 ES6 相关的包才能访问 ES6 Api
。现在,内置的标准库声明已经模块化,TypeScript 允许我们选择包含哪种类型声明。
--lib 编译器选项
JS 标准库的类型声明被划分为一组 API 组。 2016 年 11 月下旬撰写本文时,定义了以下组
- dom
- webworker
- es5
- es6 / es2015
- es2015.core
- es2015.collection
- es2015.iterable
- es2015.promise
- es2015.proxy
- es2015.reflect
- es2015.generator
- es2015.symbol
- es2015.symbol.wellknown
- es2016
- es2016.array.include
- es2017
- es2017.object
- es2017.sharedmemory
- scripthos
可以通过 --lib
命令行选项或 tsconfig.json
中的 lib
属性将上述组的任何子集传递给TypeScript 编译器。TypeScript 将只注入你指定的类型;也就是说,它会将所有其他 API 组视为不存在于你的的环境中。
如果未明确提供 lib
选项,则 TypeScript 将隐式注入Web开发所需的API组。
注意:如果--lib
没有指定默认库。默认库是
- For --target ES5:
["dom", "es5", "scripthost"]
- For --target ES6:
["dom", "es6", "dom.iterable", "scripthost"]
以 es5 为 target 的 TypeScript 项目中使用 ES6 Promise
假设你正在处理一个 target
为 es5
项目,为了让它能在所有主流浏览器中运行。你的tsconfig.json
可能是这样的:
{
"compilerOptions": {
"module": "commonjs",
"target": "es5",
"noImplicitAny": true,
"strictNullChecks": true
}
}
因为 lib
选项没有指定,所以默认情况下 TypeScript 会注入 API 组 "dom"
、"es5"
和"scripthost"
。现在希望在项目中使用ES6 中原生的 Pormise
。这些在 ES5 中并没有,所以咱们需要安装一个 polyfill
来让我们的代码在旧的浏览器中运行:
npm install --save es6-promise
然后可以在入口文件中导入对应的库
import "es6-promise";
有了这个 polyfill,现在就可以在应用程序中使用 Promise
,代码也可以正常运行。然而,TypeScript 会给你一个编译时错误: Cannot find the name 'Promise'
。这是因为 Promise
的类型声明不包含在任何注入的 API 组中。
咱要让 TypeScript 知道 Promise
会在运行时存在,这就是 lib
编译器选项发挥作用的地方:
注意,一旦覆盖了默认值,就必须显式地提供所有API组,如下所示:
{
"compilerOptions": {
"module": "commonjs",
"target": "es5",
"noImplicitAny": true,
"strictNullChecks": true,
"lib": ["dom", "es5", "es2015.promise"]
}
}
现在编辑器就不会在报错了:
原文:
https://mariusschulz.com/blog/more-literal-types-in-typescript
https://mariusschulz.com/blog/built-in-type-declarations-in-typescript
交流
阿里云最近在做活动,低至2折,有兴趣可以看看:https://promotion.aliyun.com/ntms/yunparter/invite.html?userCode=pxuujn3r
干货系列文章汇总如下,觉得不错点个Star,欢迎 加群 互相学习。
因为篇幅的限制,今天的分享只到这里。如果大家想了解更多的内容的话,可以去扫一扫每篇文章最下面的二维码,然后关注咱们的微信公众号,了解更多的资讯和有价值的内容。