还记得我在《2020 年 JavaScript 状态调研报告小结》中提到的 2020 年全球开发者最喜欢的 JavaScript 写法是什么吗?-- 没错! TypeScript!. 鉴于在项目中使用 TypeScript 确实有以下种种好处:
帮助开发者在「运行时」而非「编译时」及时感知类型 / 语法错误 ( 注意, TypeScript 并非万能, 它并不能帮助您捕捉请求错误或环境错误);
结合编辑器, 提供智能提示, 提升开发体验;
很少被提及的一点: 更容易让服务端程序员理解代码, 方便前后端交流;
本篇文章将向您介绍 TypeScript 自 3.7 版本以来更新的一些实用特性, 希望您的代码能变得更加稳固, 优雅.
1. 新支持特性
请注意, TypeScript 是 JavaScript 类型的超集, 而非语法的超集, 因此一些符合 JavaScript 语法规范的代码, 在 TypeScript 中却可能报错, 例如:
- let x = 1
- x = 'hello world'
因此, 在现代工具链中, TypeScript 编译器甚至不被用作编译出指定版本 JavaScript 的工具 (这通常是 babel 的工作), 而是作为一种更强大的代码检查工具被使用. 但是随着 TypeScript 版本的更新, 一些新的 JavaScript 语法特性也逐渐被 TypeScript 支持, 这使得开发者在一些场景下可以摆脱 babel 编译的过程, 直接使用 TypeScript 编译生成最终的代码.
1.1 Optional Chaining & Nullish Coalescing
自 3.7 版本开始, TypeScript 支持了目前在 stage 4 阶段的 Optional Chaining https://github.com/tc39/proposal-optional-chaining ,Nullish Coalescing 语法.
1.2 Private Fields && Namespace exports
自 3.8 版本开始, Typescript 支持了目前在 stage 3 阶段的 Private Fields 语法.(通过底层使用 WeakMap 数据结构, 该语法使得 JavaScript 类真正意义上拥有「私有属性」)
class Foo { #bar }
同时, 该版本还支持了 namespace exports 语法:
export * as utils from './utils' 1.3 Inference of class field types
自 4.0 版本开始, TypeScript 支持自动推导 class 中的属性类型, 无需再显示声明.
2. 新类型: 元组
TypeScript 4.0 开始支持两种新的元组类型声明方式:
Variadic tuple types(可变元组类型) Labeled tuple types(命名元组类型)
2.1 可变元组类型
type Foo<T extends any[]> = [boolean, ...T, boolean]
通过这种声明方式, 我们可以更精确的定义一个函数参数类型, 在使用函数式编程时, 这种定义方式就很有用.
2.2 命名元组类型
const Address = [string, number] function setAddress(...args: Address) { // some code here }
当我们这样定义函数入参后, 在使用函数时, 编辑器的智能提示只会提示我们参数类型, 丢失了对参数含义的描述. 为了改善这一点, 我们可以通过 Labeled tuple types, 我们可以这样定义参数:
const Address = [streetName: string, streetNumber: number] function setAddress(...args: Address) { // some code here }
这样, 在调用函数时, 我们的参数就获得了相应的语义, 这使得代码更加容易维护.
3. 可递归调用类型
自 Typescript 3.7 开始, 我们终于获得了只用一条类型声明声明 JSON 数据的能力:
type JSONValue = | string | number | boolean | null | JSONValue[] | { [key: string]: JSONValue }
4. 错误和断言处理
4.1 // @ts-expect-error
TypeScript 3.9 给出了一个替代 // @ts-ignore 注释的方案:// @ts-expect-error.
从字面上我们不难理解为什么后者是更优的选择:
显示声明了会报错的原因, 而不只是一味的规避检查;
向前兼容, 未来如果 TypeScript 支持某语法导致不再报错, TypeScript 会主动提示删除注释, 这会让代码变得更加简洁;
4.2 unknown 类型
让我们想想这段代码会最终打印出什么:
try { willThrowAnError() } catch (err) { console.log(typeof err.message) }
答案是 "string" 吗, 并非如此! 因为 err.message 的值有可能是 undefined, 甚至有可能在这里抛错, 这取决于我们的函数 willThrowAnError 内部是如何定义的:
// err.message => undefined function willThrowAnError() { throw 'hello world' } // err.message => throw an Error! function willThrowAnError() { throw null }
虽然第二种情况几乎不会发生, 但这两个示例说明了 catch 参数类型的不确定性 (因此在 TypeScript 中, 它的默认类型是 any).
因此, 在 TypeScript 4.0 中, 提供了 unknown 类型供我们处理这些我们「不知道」的类型. 不同于 any 类型, unknown 是 TypeScript 中的第一类型, 可以在任何地方使用.
子曰: 知之为知之, 不知为不知.
4.3 Assertion functions
自 TypeScript 3.7 开始, 支持基于 return/throw 的类型断言.
function assertIsArray(val: any): asserts val is any[] { if (!Array.isArray(val)) throw new Error(`${val} is not an array`) }
这将让测试变得更加容易.
点击此处浏览相关文档
5. 模块
5.1 Type-only imports
自 TypeScript 3.8 开始, TypeScript 支持仅引入模块类型:
import type { SomeThing } from "./some-module.js"; export type { SomeThing };
这样做的好处在于, 当某个模块中包含副作用代码时, 用户如果直接引入模块, 就会无意间执行副作用代码, 但当通过声明只引入类型时, 则避免了这个隐患.
点击此处浏览相关文档.
6. 小结
来源: https://segmentfault.com/a/1190000039977951