需求背景
最近参与了一个基于Angular的前端项目,在前后端交互的过程中,我们经常会遇到接口返回的Json数据中值为null的情况,如果前端在绑值时不做校验,恰好接口返回的数据值为null时,就会出现报错并给出类似报错信息
Cannot read property 'xxxx' of undefined
假设正常情况下接口返回的数据格式如下:
{
"baseInfo": {
"name": "yuzhiqiang",
"age": 27,
"hobby": [
"吃",
"喝",
"玩",
"乐"
]
},
"bankCard": [
{
"no": "6228482398431xxxxxx",
"bankName": "中国农业银行",
"type": "借记卡"
},
{
"no": "6228482398431xxxxxx",
"bankName": "中国银行",
"type": "借记卡"
},
{
"no": "6228482398431xxxxxx",
"bankName": "中国银行",
"type": "信用卡"
}
]
}
则前端对应的model如下:
class Person{
baseInfo:BaseInfo=new BaseInfo()
bankCard:BankCard[]=[]
}
/*基本信息*/
class BaseInfo{
name:string=''
age:number=0
hobby:string[]=[]
}
/*银行卡*/
class BankCard{
no:string=''
bankName:string=''
type:string=''
}
理想情况下页面的绑值代码如下:
<div>姓名:{{person.baseInfo.name}}</div>
<div>年龄:{{person.baseInfo.age}}</div>
<br>
<div *ngFor="let item of person.baseInfo.hobby">
爱好:{{item}}
</div>
<br>
<div *ngFor="let item of person.bankCard">
<p>银行:{{item.bankName}}</p>
<p>卡号:{{item.no}}</p>
<p>银行卡类型:{{item.type}}</p>
</div>
假如接口返回的数据是这样
{
"baseInfo": null,
"bankCard": []
}
按照之前的写法就会报错
为了解决这种报错并保证项目的 “健壮性”, 在绑值的时候就要先对数据进行校验,代码如下:
<div>姓名:{{person&&person.baseInfo&&person.baseInfo.name?person.baseInfo.name:''}}</div>
这样写确实能解决问题,但是写起来很恶心,我写完第一个就已经受不了了。这样的代码完全没有什么技术含量,还使得页面看起来很乱很复杂。
下面我们来看一下如何能够不写这么多恶心的校验代码,并且保证程序正常的执行。
解决方案
方案一:前后端统一好数据结构以及类型后,接口严格按照标准来返回数据,禁止返回null
这种做法一般是后端定义了个兼容前端model的业务Model,在返回数据给前端的时候做了处理。即使从表中查出来的数据是null,也会给出对应类型的初始值而不是null。
也就是说前端默认接口返回的数据一定是合法的,不会有问题的。
这种方案就需要前后端能够完全的协商好,并且后端愿意配合你这么做才能放心大胆的去用。
但是你没办发保证每一个后端都能帮你做处理,至少我遇到的后台开发很少有人愿意这么做,他们会觉得 完全没必要 或 太麻烦
这种情况下要么你能打赢他,强制他听你的,要么你是他领导,他能听你的话。
但是我断定跟你合作的后端不愿意这么做,因为如果他愿意的话,你也不会看到这篇文章了…
方案二:后端返回接口时过滤掉值为null的字段,前端拿到数据后做个 跟本地初始化的数据做一个merge 的操作
方案一对于后端来讲可能会比较麻烦,过滤掉值为null的字段再把数据给你对他们来讲还是比较简单的,基本上全局配置下就行了。以.net 为例,只需要几行代码配置下即可。
这样一来,假如原本返回给你的数据是:
{
"baseInfo": {
"name": null,
"age": 27,
"hobby": [
"吃",
"喝",
"玩",
"乐"
]
},
"bankCard": [
{
"no": "6228482398431xxxxxx",
"bankName": "中国农业银行",
"type": "借记卡"
},
{
"no": "6228482398431xxxxxx",
"bankName": "中国银行",
"type": "借记卡"
},
{
"no": "6228482398431xxxxxx",
"bankName": "中国银行",
"type": "信用卡"
}
]
}
那么过滤后返回的数据就是这样的:
{
"baseInfo": {
"age": 27,
"hobby": [
"吃",
"喝",
"玩",
"乐"
]
},
"bankCard": [
{
"no": "6228482398431xxxxxx",
"bankName": "中国农业银行",
"type": "借记卡"
},
{
"no": "6228482398431xxxxxx",
"bankName": "中国银行",
"type": "借记卡"
},
{
"no": "6228482398431xxxxxx",
"bankName": "中国银行",
"type": "信用卡"
}
]
}
过滤后就不会返回给你name这个字段了,我们拿到数据后跟本地初始化好的数据进行一次merge操作即可。
这里要注意的是 merge 不是简单的数据浅拷贝。
类似于 Object.assign()
这种方法是肯定不行的,因为他是浅拷贝,会出现数据直接被覆盖的情况。
这里给大家推荐个开源库 Lodash
Lodash 是一个一致性、模块化、高性能的 JavaScript 实用工具库,提供了很多方法供我们调用,里面有个 对象的merge 方法,正好是我们所需要的。
使用起来非常简单,直接看文档即可。
大致用法如下:
/*本地初始化的数据*/
person:Person=new Person()
/*合并数据*/
Lodash.merge(this.person,apiValue)
执行后结果如下:
这样一来我们就无需再写这么多恶心的字段值校验代码了,美滋滋啊。
关于如何在Angular中如何使用lodash请看 Angular中使用Lodash 这篇文章,里面有详细的步骤以及示例效果。
什么!!! 过滤null值后台也不愿意做?
那不好意思,建议你俩PK一下…
唉~ 先放下手中的键盘,别着急,不要慌,问题不大,都是打工人,相煎何太急啊…
来看下方案二加强版
方案二加强版:无需后端做任何操作,该怎么返回就怎么返回,前端直接Merge,Merge的时候处理一下null值
有了这个方法,你就不用去 “求” 后台帮你做处理了,直接告诉他,你随便怎么返回吧,我这都ok…
相信看过Lodash文档的同学已经发现了 Lodash中 mergeWith
这个方法了。
这个方法允许我们自定义合并的规则,我们可以这样定义,如果接口给的值是null,我们就拿默认值,否则,就以接口给的值为准。
代码如下:
Lodash.mergeWith(this.person,apiValue,(localData,apiData)=>
apiData===null?localData:undefined
)
这样写的话,就算接口返回给你的数据字段值为null,哪怕少返回了字段,我们也可以处理成本地的初始值,再也不用写那些恶心的值校验代码了,还能提升不少编码效率。
好了,以上就是本篇博客的内容了,希望能帮到你。
如果你觉得本文对你有帮助,麻烦动动手指顶一下,可以帮助到更多的开发者,如果文中有什么错误的地方,还望指正,转载请注明转自喻志强的博客 ,谢谢!