1. 首页

【TypeScript 进化史 — 8】字面量类型扩展 和 无类型导入

在我上一篇更好的类型推断的文章中,解释了 TypeScript 如何用 const 变量和 readonly 属性的字面量始化来推断字面量类型。这篇文章继续讨论这个,扩展和非扩展字面量类型之间的区别。

扩展字面量类型

当使用 const 关键字声明局部变量并使用字面量值初始化它时,TypeScript 将推断该变量的字面量类型


const stringLiteral = "https"; // Type "https" const numericLiteral = 42; // Type 42 const booleanLiteral = true; // Type true

由于 const 关键字,每个变量的值都不能更改,所以字面量类型非常有意义。它保存了关于被赋值的确切信息。

如果如果 let 声明上述的变量,那么每个字面量类型都被扩展为相应的扩展类型:


let widenedStringLiteral = stringLiteral; // Type string let widenedNumericLiteral = numericLiteral; // Type number let widenedBooleanLiteral = booleanLiteral; // Type boolean

const 变量相反,使用 let 声明的变量是可以修改的。如果 TypeScript 为 let 变量推断一个字面量类型,那么尝试为指定的值以外的任何值赋值都会在编译时产生错误。

因此,对于上述每个let变量,都会推断出扩展的类型,枚举字面量也是如此:


enum FlexDirection { Row, Column } const enumLiteral = FlexDirection.Row; // FlexDirection.Row 类型 let widenedEnumLiteral = enumLiteral; // FlexDirection 类型

总结一下,下面是扩大字面量类型的规则:

  • 字符串字面量类型被扩展为 string 类型
  • 数字字面量类型被扩展为 number 类型
  • 布尔字面量类型被扩展为 boolean 类型
  • 枚举字面量类型被扩展为包含枚举的类型

到目前为止,咱们一直在研究字面量类型的扩展,在必要时自动扩展。现在来看看非扩展字面量类型,如名所示,它们不会自动地扩展。

非扩展字面量类型

可以通过显式地将变量标注为字面量类型来创建非扩展字面量类型的变量


const stringLiteral: "https" = "https"; // 类型 "https" (非扩展) const numericLiteral: 42 = 42; // 类型 42 (非扩展)

将非扩展字面量类型的变量的值赋给另一个变量,该变量将不会扩展。


let widenedStringLiteral = stringLiteral; // 类型 "https" (非扩展) let widenedNumericLiteral = numericLiteral; // 类型 42 (非扩展)

非扩展字面量类型的好处

为了理解非扩展字面量类型的是有用的,咱们再来看看扩展字面量类型。在下面的例子中,一个数组是由两个可扩展字符串字面量类型的变量创建的:


const http = "http"; // Type "http" (可扩展) const https = "https"; // Type "https" (可扩展) const protocols = [http, https]; // Type string[] const first = protocols[0]; // Type string const second = protocols[1]; // Type string

TypeScript 推断数组 protocols 的类型为 string[]。因此,像 firstsecond 这样的数组元素类型被扩展为 string。字面量类型 "http""https" 的概念在扩展过程中丢失了。

如果咱们显式地将这两个常量指定为非扩展类型,则 protocols 数组将被推断为类型 ("http" | "https")[],它表示一个数组,其中仅包含字符串 "http""https":


const http: "http" = "http"; // Type "http" (非扩展) const https: "https" = "https"; // Type "https" (非扩展 const protocols = [http, https]; // Type ("http" | "https")[] const first = protocols[0]; // Type "http" | "https" const second = protocols[1]; // Type "http" | "https"

现在 firstsecond 的类型被推断为 "http" | "https"。这是因为数组类型没有对索引 0 处的值 "http" 和索引 1 处的值 "https" 进行编码。它只是声明该数组只包含两个字面量类型的值,不管在哪个位置。

如果出于某种原因,希望保留数组中字符串字面量类型的位置信息,可以用如下的方式显示指定:


const http = "http"; // Type "http" (可扩展) const https = "https"; // Type "https" (可扩展) const protocols: ["http", "https"] = [http, https]; // Type ["http", "https"] const first = protocols[0]; // Type "http" (非扩展) const second = protocols[1]; // Type "https" (非扩展)

现在,firstsecond 被推断为各自的非扩展字符串字面量类型。

无类型导入

从TypeScript 2.1 开始处理无类型化导入更加容易。以前,编译器过于严格,当导入一个没有附带类型定义的模块时,会出现一个错误:

Js中文网 · 前端进阶资源教程 www.javascriptc.com

从 TypeScript 2.1 开始,如果模块没有类型声明,编译器将不再报错。

Js中文网 · 前端进阶资源教程 www.javascriptc.com

现在,导入的 range 函数的类型为 any。这样做的好处是,将现有的 JS 项目迁移到 TypeScrip t可以减少编译时错误。缺点是,不会得到任何自动完成建议或细粒度类型检查,因为编译器对模块或其导出一无所知。

如果过后提供类型声明,例如通过 npm 的类型声明包,它们将优先于默认的任何类型。(否则,将无法为导入的模块提供类型)

对于没有声明文件的模块的导入,在使用了--noImplicitAny编译参数后仍将被标记为错误。

// Succeeds if `node_modules/asdf/index.js` exists
import { x } from "asdf";

支持--target ES2016,--target ES2017--target ESNext

TypeScript 2.1支持三个新的编译版本值--target ES2016,--target ES2017--target ESNext

使用target--target ES2016将指示编译器不要编译ES2016特有的特性,比如**操作符。

同样,--target ES2017将指示编译器不要编译ES2017特有的特性像async/await

--target ESNext则对应最新的ES提议特性支持.

改进any类型推断

以前,如果 TypeScript 无法确定变量的类型,它将选择any类型。


let x; // 隐式 'any' let y = []; // 隐式 'any[]' let z: any; // 显式 'any'.

使用TypeScript 2.1,TypeScript 不是仅仅选择any类型,而是基于你后面的赋值来推断类型。

仅当设置了--noImplicitAny编译参数时,才会启用此选项。

示例

let x;

// 你仍然可以给'x'赋值任何你需要的任何值。
x = () => 42;

// 在刚赋值后,TypeScript 2.1 知道'x'的类型是'() => number'。
let y = x();

// 感谢,现在它会告诉你,你不能添加一个数字到一个函数!
console.log(x + y);
//          ~~~~~
// 错误!运算符 '+' 不能应用于类型`() => number`和'number'。

// TypeScript仍然允许你给'x'赋值你需要的任何值。
x = "Hello world!";

// 并且现在它也知道'x'是'string'类型的!
x.toLowerCase();

现在对空数组也进行同样的跟踪。

没有类型注解并且初始值为[]的变量被认为是一个隐式的any[]变量。变量会根据下面这些操作x.push(value)、x.unshift(value)x[n] = value向其中添加的元素来不断改变自身的类型。


function f1() { let x = []; x.push(5); x[1] = "hello"; x.unshift(true); return x; // (string | number | boolean)[] } function f2() { let x = null; if (cond()) { x = []; while (cond()) { x.push("hello"); } } return x; // string[] | null }

隐式 any 错误

这样做的一个很大的好处是,当使用--noImplicitAny运行时,你将看到较少的隐式any错误。隐式any错误只会在编译器无法知道一个没有类型注解的变量的类型时才会报告。

示例


function f3() { let x = []; // 错误:当变量'x'类型无法确定时,它隐式具有'any[]'类型。 x.push(5); function g() { x; // 错误:变量'x'隐式具有'any【】'类型。 } }

往期阅读

作者:Marius Schulz
来源:https://mariusschulz.com/

看完两件小事

如果你觉得这篇文章对你挺有启发,我想请你帮我两个小忙:

  1. 关注我们的 GitHub 博客,让我们成为长期关系
  2. 把这篇文章分享给你的朋友 / 交流群,让更多的人看到,一起进步,一起成长!
  3. 关注公众号 「画漫画的程序员」,公众号后台回复「资源」 免费领取我精心整理的前端进阶资源教程

JS中文网是中国领先的新一代开发者社区和专业的技术媒体,一个帮助开发者成长的社区,目前已经覆盖和服务了超过 300 万开发者,你每天都可以在这里找到技术世界的头条内容。欢迎热爱技术的你一起加入交流与学习,JS中文网的使命是帮助开发者用代码改变世界

本文著作权归作者所有,如若转载,请注明出处

转载请注明:文章转载自「 Js中文网 · 前端进阶资源教程 」https://www.javascriptc.com

标题:【TypeScript 进化史 — 8】字面量类型扩展 和 无类型导入

链接:https://www.javascriptc.com/3358.html

« 如何利用 BLoC 在 Flutter 和 AngularDart 中共享代码?
「圣诞特辑」纯前端实现人脸识别自动佩戴圣诞帽»
Flutter 中文教程资源

相关推荐

QR code