可选链式调用optional-chaining

  • 2019.8 Stage 2 -> Stage 3

引导

当我们访问一个深层树形结构的对象时,我们总需要判断中间节点属性是否存在:

1
let street = user.address && user.address.street;

或者在DOM操作获取值的时候也需要先判断节点是否存在:

1
2
let fooInput = document.querySelector('input[name=foo]');
let fooValue = fooInput ? fooInput.value : undefined;

虽然为了不报错,我们不得不加上这种判断,有时候甚至更加麻烦。

始终觉得这类操作很麻烦是不,这就是接下来 Optional chaining大放光彩的时刻

Optional chaining

optional chaining(可选练市调用)就是添加了?.这么个操作符这么简单,它会先判断前面的值,如果是 null 或 undefined,就结束调用并返回 undefined。

Optional chaining是一个在2018.11月提交的方案,当前状态为stage 3(2019.8)。

Optional Chaining 的语法有三种使用场景:

1
2
3
obj?.prop       // optional static property access
obj?.[expr] // optional dynamic property access
func?.(...args) // optional function or method call

有了optional chaining,上面的麻烦写法就可以简化成这样:

1
2
let street = user.address?.street
let fooValue = document.querySelector('input[name=foo]')?.value

再看babel给出的一个案例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
const obj = {
foo: {
bar: {
baz: 42,
},
},
};

const baz = obj?.foo?.bar?.baz; // 42

const safe = obj?.qux?.baz; // undefined

// Optional chaining and normal chaining can be intermixed
obj?.foo.bar?.baz; // Only access `foo` if `obj` exists, and `baz` if
// `bar` exists

// Example usage with bracket notation:
obj?.['foo']?.bar?.baz // 42

这么多层的判断都免了,很爽。

Optional chaining 也可以用在方法上,如:

1
iterator.return?.()

或者试图调用某些未被实现的方法:

1
2
3
4
if (myForm.checkValidity?.() === false) { // skip the test in older web browsers
// form validation fails
return;
}

优先级顺序

既然?.作为运算符,那么它的运算优先级如何?可通过一下几个例子大致了解:

1
2
(a?.b).c	// -> (a == null ? undefined : a.b).c
a?.b.c // -> a == null ? undefined : a.b.c

Optional delete

1
delete a?.b		// -> a == null ? true : delete a.b

这样不论 b 是否存在,得到的都是 b 删除成功的信号(返回值 true)。

项目中使用

如此灵活好用的功能,在项目中该如何运用呢?

babel插件

首先babel给出了插件:babel/plugin-proposal-optional-chaining

安装:

1
npm install --save-dev @babel/plugin-proposal-optional-chaining

配置(如.babelrc文件)

1
2
3
{
"plugins": ["@babel/plugin-proposal-optional-chaining"]
}

更多babel配置请见参考资料中其babel插件。

TypeScript

TypeScript中并无此操作符或草案,但TypeScript给出了更直接的方案,并也有此插件ts-optchain


1
2
3
import { oc } from 'ts-optchain';
const obj: T = { /* ... */ };
const value = oc(obj).propA.propB.propC(defaultValue);

将被转换成:

1
2
3
4
const value =
(obj != null && obj.propA != null && obj.propA.propB != null && obj.propA.propB.propC != null)
? obj.propA.propB.propC
: defaultValue;

*拓展

配合另一个新特性 Nullish Coalescing 做默认值处理非常方便,它添加了??这个操作符,如

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
const response = {
settings: {
nullValue: null,
height: 400,
animationDuration: 0,
headerText: '',
showSplashScreen: false
}
};

const undefinedValue = response.settings?.undefinedValue ?? 'some other default'; // result: 'some other default'
const nullValue = response.settings?.nullValue ?? 'some other default'; // result: 'some other default'
const headerText = response.settings?.headerText ?? 'Hello, world!'; // result: ''
const animationDuration = response.settings?.animationDuration ?? 300; // result: 0
const showSplashScreen = response.settings?.showSplashScreen ?? true; // result: false

参考资料