如何优雅的链式取值

方法一:Optional Chaining

这是一个当前处于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>
)

看起来又很不优雅了。

方法三:利用Proxy

这是我自己实现的一种方法,先介绍用会有什么样的使用效果:

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。

你可能感兴趣的:(js)