背景

前端开发最头疼的问题之一必有后端的返回值,尤其是在接口没有保证但数据结构又比较复杂的时候, 准确且不报错地拿到我们期望的值是需要花费功夫的事情 (尤其在未使用typescript的时候,对接其他模块同理),所以在对接口时不得不防御性拉满, 毕竟当数据源无法保证时,ui 逻辑层就可能报一大堆的访问错误

解构赋值

试想想有这样一个数据,它唯一能保证的只有它是一个对象,但我们需要的数据却是:

const someData = {};

// 使用
console.log(someData.data[0].a, someData.data[0].b, someData.data[0].c);

我们期望的 someData 的结构是{data:[{a,b,c}]},data 是个数组,要拿到数组内第一个对象的 a,b,c 属性

当然我们可以对属性进行判断,防御拉满,层层把控,但接下来我们要尝试一下更优雅的解构赋值:

const {
  data: [{ a, b, c }],
} = someData;
// 报错
// TypeError: undefined is not iterable (cannot read property Symbol(Symbol.iterator))

报错了,需要注意的是这里的 data 只是解构时的一个匹配模式,并不是一个变量,当 someData 是个空对象时, 匹配不到 data 属性,继续把 data 当成数组解构的话,相当于遍历一个 undefined ,当然会报错

默认值

我们的目标是保证 a,b,c 能拿到,并有默认值,尝试一下

const {
  data: [{ a = [], b = [], c = [] }],
} = someData;
// 报错
// TypeError: undefined is not iterable (cannot read property Symbol(Symbol.iterator))

可以看到还是报了一样的错,这是因为当数据源 someData 内部无法保证时,直接给 a,b,c 默认值是没有用的, 因为 data 这一层的匹配错误仍然没有解决,所以我们首先要保证 data 是个数组

这里再回顾下解构的语法,左边的 data 是匹配模式,右边的[...]表示匹配的 data 继续以数组解构, 然后再是数组第一项的解构

解构语法中的默认值是赋给变量的,也就是右边的那坨[...]但需要注意的是当变量的值是null时,是无法赋默认值的

再尝试一下:

const { data: [{ a = [], b = [], c = [] }] = [] } = someData;
// 报错
// TypeError: Cannot read properties of undefined (reading 'a')

现可以看到现在仍然是报错的,但错不一样了,从错误信息可以看到已经解构进 data 了,现在出错在对象的解构上 对象解构出错的原因很明显,目前的默认值是个空数组[], 访问这个空数组的第 0 项,是个 undefined,解构自然会出错 解决办法也很简单,默认值再丰富下:

const { data: [{ a = [], b = [], c = [] }] = [{}] } = someData;

console.log(a, b, c); // [] [] []

可以看到已经能拿到默认值了,这里其实也可以把a,b,c的默认值挪到 data 的匹配变量下:

const { data: [{ a, b, c }] = [{ a: [], b: [], c: [] }] } = someData;

console.log(a, b, c); // [] [] []

但这种写法只能在 data 匹配不到时起作用,当data有值时,会出现以下情况:

const someData = { data: [] };

const { data: [{ a, b, c }] = [{ a: [], b: [], c: [] }] } = someData;

// 报错
// TypeError: Cannot read properties of undefined (reading 'a')

可以看到,由于 data 匹配到了一个空数组,然后继续按照空数组去解构了,自然会解构失败, 再看下 data 数组不为空的情况:

const someData = { data: [{}] };

const { data: [{ a, b, c }] = [{ a: [], b: [], c: [] }] } = someData;

console.log(a, b, c); // undefined undefined undefined

同样的可以看到虽然没有解构失败,但是没有默认值,因为并没有走到 data 的默认赋值那儿, a,b,c 是在空对象里解构的,拿到的都是undefined

总结

综上所诉,针对本文提到的数据结构,用以下这种解构赋值方式即可:

const someData = {};

const { data: [{ a = [], b = [], c = [] }] = [{}] } = someData;

console.log(a, b, c); // [] [] []

但根据解构赋值的匹配模式与默认值赋值过程来看,data 的默认值内附带上属性值也未尝不可, 当 data 匹配不到时,应该能减少三次默认值赋值的过程,性能可能有所提升(纯猜测,未经验证)

const someData = {};

const {
  data: [{ a = [], b = [], c = [] }] = [{ a: [], b: [], c: [] }],
} = someData;

// data属性不存在时,取右侧默认值进行解构
// 解构到第一个对象的a,b,c属性时,因为有值[],所以这三个对象都免去了空值判断和默认值赋值的过程
// 纯猜测,未经验证