如何处理对可能未定义的数据的调用函数?

时间:2018-10-03 18:30:23

标签: javascript reactjs

我主要使用React,并且经常发现当我编写依赖于组件状态的函数时,必须执行检查以查看状态是否已定义,然后再执行任何操作。

例如:我有一个函数,该函数使用.map()循环遍历从数据库中获取的对象数组,并为数组中的每个对象生成jsx。我的组件的render()函数中调用了此函数。第一次调用render()时,初始数组为空。这会导致错误,因为,当然,数组的第一个索引是未定义的。

我一直在通过有条件检查来检查数组的值是否未定义来规避此问题。每次编写一个if语句的过程都有些笨拙,我想知道是否有更好的方法来执行此检查或完全避免检查。

2 个答案:

答案 0 :(得分:4)

在使用地图之前检查数组:

arr && arr.map()

OR

arr && arr.length && arr.map() // if you want to map only if not empty array

OR

我们甚至可以这样使用(如devserkan所述):

(arr || []).map()

根据您的评论:

  

我希望有像C#(arr?.map())这样的安全导航操作符

是的,显然。仍在提议中的JavaScript中将其称为optional chaining。如果接受,则可以这样使用:

arr?.map()

您可以在staging 1中看到它,您可以使用babel preset stage1


但是很明显,除了检查数组的长度,将无法满足您的要求:

  

这会导致错误,因为,当然,数组的第一个索引未定义。

所以,我建议您使用:

arr && arr.length && arr.map()

答案 1 :(得分:0)

您在这里实际需要的称为可选链接

obj?.a?.b?.c // no error if a, b, or c don't exist or are undefined/null

?.是存在运算符,它使您可以安全地访问属性,并且在缺少属性时也不会抛出该属性。但是,可选链接尚不是JavaScript的一部分,但已提出see State 1 of TC39

但是,使用代理和Maybe类,您可以实现可选链接,并在链接失败时返回默认值。

wrap()函数用于包装要在其上应用可选链接的对象。在内部,wrap在对象周围创建代理,并使用Maybe包装器管理缺失值。

在链的末尾,您可以通过将getOrElse(default)与默认值链接来解开值,该默认值在链无效时返回:

const obj = {
  a: 1,
  b: {
    c: [4, 1, 2]
  },
  c: () => 'yes'
};

console.log(wrap(obj).a.getOrElse(null)) // returns 1
console.log(wrap(obj).a.b.c.d.e.f.getOrElse(null)) // returns null
console.log(wrap(obj).b.c.getOrElse([])) // returns [4, 1, 2]
console.log(wrap(obj).b.c[0].getOrElse(null)) // returns 4
console.log(wrap(obj).b.c[100].getOrElse(-1)) // returns -1
console.log(wrap(obj).c.getOrElse(() => 'no')()) // returns 'yes'
console.log(wrap(obj).d.getOrElse(() => 'no')()) // returns 'no'

wrap(obj).noArray.getOrElse([1]).forEach(v => console.log(v)) // Shows 1
wrap(obj).b.c.getOrElse([]).forEach(v => console.log(v)) // Shows 4, 1, 2

完整的示例:

class Maybe {
  constructor(value) {
    this.__value = value;
  }
  static of(value){
    if (value instanceof Maybe) return value;
    return new Maybe(value);
  }
  getOrElse(elseVal) {
    return this.isNothing() ? elseVal : this.__value;
  }
  isNothing() {
    return this.__value === null || this.__value === undefined;
  }
  map(fn) {  
    return this.isNothing()
      ? Maybe.of(null)
      : Maybe.of(fn(this.__value));
  }
}

function wrap(obj) {
  function fix(object, property) {
    const value = object[property];
    return typeof value === 'function' ? value.bind(object) : value;
  }
  return new Proxy(Maybe.of(obj), {
    get: function(target, property) {
      if (property in target) {
          return fix(target, property);
      } else {
        return wrap(target.map(val => fix(val, property)));
      }
    }
  });
}

const obj = { a: 1, b: { c: [4, 1, 2] }, c: () => 'yes' };

console.log(wrap(obj).a.getOrElse(null))
console.log(wrap(obj).a.b.c.d.e.f.getOrElse(null))
console.log(wrap(obj).b.c.getOrElse([]))
console.log(wrap(obj).b.c[0].getOrElse(null))
console.log(wrap(obj).b.c[100].getOrElse(-1))
console.log(wrap(obj).c.getOrElse(() => 'no')())
console.log(wrap(obj).d.getOrElse(() => 'no')())

wrap(obj).noArray.getOrElse([1]).forEach(v => console.log(v)) // Shows 1
wrap(obj).b.c.getOrElse([]).forEach(v => console.log(v)) // Shows 4, 1, 2