【TypeScript 演化史 -- 4】更多的字面量类型 与 内置类型声明

2022-06-15 13:29:29 浏览数 (1)

作者:Marius Schulz 译者:前端小智 来源:Marius Schulz


为了保证的可读性,本文采用意译而非直译。

TypeScript 1.8 引入了字符串字面量类型,用于将变量限制为可能的字符串值的有限集。在 TypeScript 2.0 中,字面量类型不再局限于字符串。以下字面量类型已添加到类型系统中:

  • boolean 字面量类型
  • 数字字面量类型
  • 枚举字面量类型

接下来,来看看这个类型对应的一些事例。

boolean 字面量类型

下面的示例定义了两个常量 TRUEFALSE,它们分别持有 true false 值:

代码语言:javascript复制
const TRUE: true = true; // OK
const FALSE: false = false; // OK

试图为每个局部变量分配相反的布尔值会导致类型错误:

代码语言:javascript复制
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 的联合类型:

代码语言:javascript复制
let value: true | false; // Type boolean

虽然 boolean 字面量类型在隔离时很少有用,但它们与标记联合类型和基于控制流的类型分析结合使用时非常有效。例如,可以定义一个泛型 Result <T>类型,该类型要么包含一个类型为 T 的值,要么包含一个类型为 string 的错误消息,如下所示

代码语言:javascript复制
type Result<T> =
  | { success: true; value: T }
  | { success: false; error: string };

这是一个接受参数的函数:

代码语言:javascript复制
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 的类型的值,必须在联合类型中明确包含 nullundefined 类型。

我们现在可以像下面这样调用 parseEmailFunction

代码语言:javascript复制
const parsed = parseEmailAddress("example@example.com");

if (parsed.success) {
  parsed.value; // OK
  parsed.error; // Error
} else {
  parsed.value; // Error
  parsed.error; // OK
}

请注意,某些属性访问表达式用红色波浪线下划线:

这样做的好处是,编译器仅在检查了 parsed.success 后才允许咱们使用valueerror属性:

  • 如果 parsed.successtrue,则 parsed 的类型必须为 { success: true; value: string }。在这种情况下,咱们可以访问 value,但不能访问 error
  • 如果 parsed.successfalse,则 parsed 的类型必须为 { success: false; error: string }。在这种情况下,咱们可以访问 error,但不能访问 value

数字字面量类型

与字符串字面量类型类似,我们可以将数值变量限制为已知值的有限集

代码语言:javascript复制
let zeroOrOne: 0 | 1;

zeroOrOne = 0;
// OK

zeroOrOne = 1;
// OK

zeroOrOne = 2;
// 错误:类型 '2' 不能分配给类型 '0 | 1'

在实践中,我们可以在处理端口号时使用数字字面量。不安全的 HTTP 使用端口 80,而 HTTPS 使用端口 443。咱们可以编写一个 getPort 函数,并在其函数签名中编码仅有的两个可能的返回值

代码语言:javascript复制
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函数的不同重载提供更具体的类型。

代码语言:javascript复制
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 编译器可以帮助咱们检测错误的逻辑和无效的代码。

枚举字面量类型

最后,咱们还可以使用枚举作为字面量类型。继续前面的示例,实现一个给定端口(80443)映射到相应方案(分别为 HTTPHTTPS)的函数。为此,我们首先声明一个const enum,它对两个端口号进行构建:

代码语言:javascript复制
const enum HttpPort {
  Http = 80,
  Https = 443
}

现在是 getScheme 函数,再次使用函数重载来实现专门的类型注解:

代码语言:javascript复制
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 代码,去掉了注解:

代码语言:javascript复制
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

假设你正在处理一个 targetes5 项目,为了让它能在所有主流浏览器中运行。你的tsconfig.json 可能是这样的:

代码语言:javascript复制
{
  "compilerOptions": {
    "module": "commonjs",
    "target": "es5",
    "noImplicitAny": true,
    "strictNullChecks": true
  }
}

因为 lib 选项没有指定,所以默认情况下 TypeScript 会注入 API 组 "dom""es5""scripthost"。现在希望在项目中使用ES6 中原生的 Pormise。这些在 ES5 中并没有,所以咱们需要安装一个 polyfill 来让我们的代码在旧的浏览器中运行:

代码语言:javascript复制
npm install --save es6-promise

然后可以在入口文件中导入对应的库

代码语言:javascript复制
import "es6-promise";

有了这个 polyfill,现在就可以在应用程序中使用 Promise,代码也可以正常运行。然而,TypeScript 会给你一个编译时错误: Cannot find the name 'Promise'。这是因为 Promise 的类型声明不包含在任何注入的 API 组中。

咱要让 TypeScript 知道 Promise 会在运行时存在,这就是 lib 编译器选项发挥作用的地方:

注意,一旦覆盖了默认值,就必须显式地提供所有API组,如下所示:

代码语言:javascript复制
{
  "compilerOptions": {
    "module": "commonjs",
    "target": "es5",
    "noImplicitAny": true,
    "strictNullChecks": true,
    "lib": ["dom", "es5", "es2015.promise"]
  }
}

现在编辑器就不会在报错了:


编辑中可能存在的bug没法实时知道,事后为了解决这些bug,花了大量的时间进行log 调试,这边顺便给大家推荐一个好用的BUG监控工具 Fundebug

原文:

https://mariusschulz.com/blog... https://mariusschulz.com/blog...


0 人点赞