防御性编程之解构赋值
背景
前端开发最头疼的问题之一必有后端的返回值,尤其是在接口没有保证但数据结构又比较复杂的时候, 准确且不报错地拿到我们期望的值是需要花费功夫的事情 (尤其在未使用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属性时,因为有值[],所以这三个对象都免去了空值判断和默认值赋值的过程
// 纯猜测,未经验证