作者:ICBU 东墨
optional-chaining 是一个 Javascript 中可以增强惰性求值时健壮性的特性,2019 年 3 月,v8 实现了该特性, 此后一段时间,在 chrome 中作为一项实验性 flag 被打开。在本文中, 如无特别说明, 所有的代码均在 typescript 运行. 在 typescript 3.7 之前, ts 并未原生支持 optional-chaining 操作符; 在 2019 年 11 月发布的 typescript 3.7 中, ts 已支持 optional-chaining 操作符为一级特性.
快速处理一个数状数据结构
我们往往会在处理数据时遇到需要层层读取树状数据的情况,往往是为了取得某个层级的数据. 考虑下面这样来自于后端 API 的数据, 我们需要从一条 user 记录中, 取得其首 primary office 的 city name:
const apiResult = {
name: "Neville Bowers",
office: {
primary: {
city: "San Francisco",
state: "CA"
}
}
}
为了得到想要的数据,我们会这样写:
const city = apiResult.office.primary.city;
// --> "San Francisco"
简单明了. 然而, 这段代码成立的潜在前提是, API 返回总是会在运行时返回稳定的结构. 但在实际中, 我们会遇到 API 返回的结果中, user 结构中缺少 office 字段的情况:
const apiResult = {
name: "Yatharth Agarwal"
}
const city = apiResult.office.primary.city;
// --> Uncaught TypeError: Cannot read property 'primary' of null
这就有问题. 进一步地说,如果我们没有想到要去 catch 异常,我们的应用可能就会崩溃!显然我们需要在层层读取 apiResult
的时候更加小心 —— 往往事后想起来我们觉得理所应当,但是开发时很可能忽略的,尤其是当我们的 API 在大多数时候返回稳固的数据结构时,或者是相关的 API 文档未给出返回数据的所有可能状态。
对于这种情况,我们最保守的处理手段是假设在这种树状结构中,每个层级的所有字段都是可能不存在的. 让我们来探索一下有什么具体的手段来处理可能缺失的字段.
a/ 层层判定是否存在的逻辑
我们可以层层判定某个字段是否存在,以此确定取值逻辑。
let city: string | undefined = undefined;
if (apiResult) {
const office = apiResult.office;
if (office) {
const primary = office.primary;
if (primary) {
city = primary.city;
}
}
}
这当然是个有效的方法, 可以对运行时中不可预期的 apiResult
结构做弹性处理, 但这段代码太冗长难读. 一些开发者会把这种代码叫做"死亡金字塔". 另外, 这种写法需要我们用 let
使得 city
可被重新赋值,就代码规范而言这是一种开倒车。
b/ 层层递进的三元表达式
我们可以使用层层递进的三元表示来安全地获取 city
字段:
const city = !apiResult
? undefined
: !apiResult.office
? undefined
: !apiResult.office.primary
? undefined
: apiResult.office.primary.city;
这种方法比上一种方法简洁些(译者注:哪里简洁了……),并且允许我们保持 city 的不可变性(使用了 const); 但是这段代码依然是难读且有冗余的(比如, undefined
出现了 3 次).
c/ 逻辑表达式
我们也可以使用逻辑表达式来安全地取得 city 字段:
const city =
apiResult &&
apiResult.office &&
apiResult.office.primary &&
apiResult.office.primary.city;
这段代码比层层递进的三元表达式又简洁了一些(译者注: 并没有……), 然而它依然很冗长.
d/ try/catch 块
我们还可以使用 try/catch 块来安全地取得 city 字段:
let city: string | undefined = undefined;
try {
city = apiResult.office.primary.city;
} catch (error) {
// Swallow Error
}
try/catch 这种途径最接近"直接读取". 但如果为了取值就这样写的话, 就会在代码里写出大量的 try/catch 块, 而且这里的 city
变量也得是可被重新赋值的变量.
e/ lodash, ramda 等
一些库提供了安全的进行树状结构梳理的方法. 比如, 在 lodash
中, 我们可以使用 get
方法来安全地得到 city
:
import * as _ from 'lodash';
const city = _.get(apiResult, 'office.primary.city', undefined);
这种方案是到目前为最简洁的方法, 代码长度只比"直接读取"长了一点点. 然而, 为此我们付出了开发时的代价:
- 在层层取值时候的 IDE 提示.
- 对取值路径
office.primary.city
的编译器类型推导(即city
的类型是不确定的, 除非你明确指定)
Optional-Chaining 操作符
上文提到每种对树状数据层层取值方案都有其短板. 那么有没有一种更完美的方案?
有, 就是 Optional-Chaining 操作符
// if `a` is `undefined` or `null`:
// return `undefined`
// else:
// return `a.b`
a?.b;
// The optional chaining operator is equivalent to:
(a == null) ? undefined : a.b;
( 以上引用自 github.com/tc39/propos… )
在别的语言中, 这种操作符往往被命名为为 safe navigation 操作符, existential 操作符, null-conditional 操作符, 或者 null propagation operator.
Optional Chaining 操作符可以组合起来使用, 以优雅地处理需要从树状数据层层取值的场景: 在连续使用的 Optional Chaining 操作符中, 如果中间有任何地方返回 null
或者 undefined
, 整个联调会返回 undefined
.
回到我们的例子, 如果使用 Optional Chaining 操作符, 写法会是这样:
const city = apiResult?.office?.primary?.city;
// --> string | undefined
你看, 这种语句对我们的问题就是一个非常直接的解决方案. 它很简洁, 可读性高, 且保留了所有的开发时工具提示.
在 TypeScript 在 3.7 之前不支持这个特性, 在那之前, TypeScript 在等待TC39 确认是否要将对 Optional-Chaining 操作符的支持列入标准. 如果你还在使用 TypeScript 3.7 之前的版本, 可以通过 ts-optchain 这个包来使用 optional-chaining.
常见问题FAQ
- 免费下载或者VIP会员专享资源能否直接商用?
- 本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
- 提示下载完但解压或打开不了?
- 找不到素材资源介绍文章里的示例图片?
- 模板不会安装或需要功能定制以及二次开发?
发表评论
还没有评论,快来抢沙发吧!