这是一个当前处于stage 2的ecma新语法,babel官方已经实现了语法插件,不过目前还没放到自己的哪个presets里,可以直接通过plugins使用:babel-plugin-transform-optional-chaining
按照这种语法代码就可以这么写:
const MyComponent = props => (
<div>
{props?.user?.info?.addresss?.[0]?.city || '暂无城市信息'}
</div>
)
只要能习惯“?”的存在,没什么问题。
【此处有过更正】经过提醒,我去仔细看了一遍proposal,发现确实支持数组。
尝试用了一下这个plugin,需要注意的是:需要babel 7才能解析这种语法,而babel 7还在alpha阶段:Planning for 7.0。简单地用一下babel-cli还可以,在一个实际的webpack项目里强行使用babel-core@next,结果编译无法通过。想要正式地使用这个语法还要等一段时间。
lodash里提供了一个函数_.get,传送门:Lodash Documentation。
用起来如下:
import { get } from 'lodash'
const MyComponent = props => (
<div>
{get(props, 'user.info.address[0].city', '暂无城市信息')}
</div>
)
能解决问题,支持[ ],但是用字符串描述总觉得很不优雅,比方说里面有点动态内容:
import { get } from 'lodash'
const MyComponent = props => (
<div>
{get(props, `user.info.address[${props.index}].city`, '暂无城市信息')}
</div>
)
看起来又很不优雅了。
这是我自己实现的一种方法,先介绍用会有什么样的使用效果:
import pointer from './pointer'
const MyComponent = props => (
<div>
{pointer(props).user.info.address[0].city('暂无城市信息')}
</div>
)
支持方括号,不拼字符串,唯一看着有点多余的是开头的pointer(…),不过这已经比较接近我心目中的优雅了。
缺点也是有的,这种方法依赖Proxy,目前的浏览器覆盖还不够好,而且还无法完美polyfill,存在的polyfill无法在这里起到效果。但如果只考虑先进的浏览器,那么这是目前我最喜欢的方案了。
Proxy是ES6规范中的一个对象类型,可以“劫持”对象的各种操作,比方说你可以在浏览器(当然要先进的浏览器)的console里试着运行以下代码:
let a = { x: 1 };
let b = new Proxy(a, {
get (target, key) {
return a[key] || 'Get away, nothing here!!';
}
});
console.log(b.x); // 1
console.log(b.y); // Get away, nothing here!!
console.log(b.z); // Get away, nothing here!!
利用这种特性可以通过劫持get行为在取值的时候根据情况返回东西,那么我只需要永远不返回undefined或null,就不会在取值的时候报错。
以下是我的实现:
// pointer.js
const dummy = () => {}
let G
(function () {
G = this
})() // 取得global或window,兼容Node与浏览器环境
function softBind (func, context) {
return function (...args) {
if (this === G) {
func.call(context, ...args)
} else {
console.log(context);
func.call(this, ...args)
}
}
}
function pointer (root, path = []) {
return new Proxy(dummy, {
get (target, property) {
return pointer(root, path.concat(property))
},
apply (target, self, args) {
let val = root
let parent
for (let i = 0; i < path.length; i++) {
if (val === null || val === undefined) {
break
}
parent = val
val = val[path[i]]
}
if (typeof val === 'function') {
val = softBind(val, parent)
}
if (val === null || val === undefined) {
val = args[0]
}
return val
}
})
}
export default pointer
本质思路是pointer返回的Proxy在取值的时候会再返回一个pointer,每一个pointer只记录了“根对象”以及“取值路径”,只有在最后当做函数调用的时候才会根据二者获取实际的内容,中途遇到会报错的null或者undefined就直接返回默认值。
这段代码里有个softBind可能会显得比较令人费解,这主要是为了支持这种用法:
pointer(obj).field.myMethod()()
即被取值的内容是个函数,而且会被立刻调用,这种情况下如果不做专门处理this会错误(函数执行时浏览器里this成了window而不是期望中的obj.field)。这里可以简单地用bind来解决,但是我觉得可能会造成问题(比如用户取到值之后自己bind或者调用apply,指定其他的东西作为this),所以我会加个判断,只有当函数执行发现this是global或者window的时候再使用取值来源作为this。