避开数据中繁琐又无效率的&& ...&& 判断符

124 阅读5分钟

背景

随着web开发的不断发展,前后端分离早已从一个趋势变成了项目开发的日常配置,分离之后分工更加明确,开发效率提升,但是同时,接口也成为了前后端沟通的唯一渠道,前端页面对接口数据准确性的依赖程度空前提高。通常前后端商讨定义好api格式,正常运行起来也并没有什么问题,但事情往往并不会这么理想。。。

出现的问题 复现一个前端生涯中经常出现的场景:

产品(气喘吁吁):快!用户反馈页面白页了,你们前端快看一下! 前端:不能吧~ 最近没上过线啊 前端(一顿debug之后):接口有个字段没传,你去找RD问一下吧 前端心里:不是我的锅,干嘛每次都来吓我 常见原因分析 某字段没传:js可以尝试读取一个对象中不存在的属性,但如果连这个对象都不存在,js就会报错,并阻止后续代码的执行。 /* api预定格式: { code:0, data:{ house:{ name: 'XX公寓', tags: ['月租', ‘独卫’] } } } 实际返回: { code:0, data:{} } / const response = await http.post(url); // 返回的house字段不存在,下面这行会报错,后面的代码都不会执行 localName = response.data.house.name; 字段数据类型错误: / api预定格式: { code:0, data:{ house:{ name: 'XX公寓', tag: ['月租', ‘独卫’] } } } 实际返回: { code:0, data:{ house:{ name: 'XX公寓', tag: {} } } } */ const response = await http.post(url); // 返回的格式不是数组,对象没有pop方法,报错并停止执行 lastTag = response.data.house.tags.pop(); 数据超过范围:比如字符串长度超过接口定义,导致页面元素位置错乱。 定位根本原因 接口双方应该本着互不信任的原则,在执行逻辑之前要对数据做校验。为了防止攻击者伪造请求进行攻击,后端往往要做大量的数据校验。 但是前端请求的是自己的服务器,安全性很有保障,所以大多数情况下前端不会对后端返回的数据做校验,这通常不会有什么问题,当然,是在产品气喘吁吁跑来找你之前。

尝试解决方案 手动校验每个字段,类似这样: /* api预定格式: { code:0, data:{ house:{ name: 'XX公寓', tag: ['月租', '独卫'] } } } */ const response = await http.post(url); if (response.code!=0){console.log('code 错误')} if (!response.data){console.log('data 缺失')} if (response.data&&!response.data.house){console.log('data.house 缺失')} // ......验证其他字段,不想写了,已经吐了 结论:重复劳动,极其繁琐,非常不建议用这种方案 Typescript:Ts不是强类型么?用Ts啊! 结论:Ts是静态的,而且只在编译时候有用,对这种接口动态返回的数据无能为力。 最后采用的方案 既然校验数据这么麻烦,用nodejs写的服务端接口是怎么搞的呢? 查了一圈发现了一个叫class-validator的东西,是nestjs框架里的默认工具。这玩意儿能用在前端吗?

能!使用起来大概长这个样子 import {validate, ValidateNested,IsNotEmpty,Equals, Length, IsArray} from "class-validator"; let hasOwnProperty = Object.prototype.hasOwnProperty; let propIsEnumerable = Object.prototype.propertyIsEnumerable;

class House { @Length(10, 20,{message: 'name的长度不能小于10不能大于20'}) @IsNotEmpty({message:'name 不能为空'}) name: string;

@IsArray({message:'数组 不能为空'})
tags: object;

}

class Data { @ValidateNested() house: House;

constructor() {
    this.house = new House();
}

}

class ResponseData { @ValidateNested() data: Data;

@Equals(0)
code: number

constructor() {
    this.data = new Data();
}

}

let data = { code:0, data:{ house:{ name: '', tags: ['整租'] }, }, }

let post = new ResponseData();

deepAssign(post,data)

validate(post).then(errors => { if (errors.length > 0) { console.log(errors); } else { console.log("成功啦!"); } }); 结论 校验属性值的报错信息可以自定义信息,像这样 @IsArray({message:'数组 不能为空'}) 也可以使用默认的信息,像这样

@Equals(0) 报错信息 validate 方法返回的是包含ValidationError对象的一个数组 [ { "target": { "data": { "house": { "name": "dddd", "tags": [ "整租" ] } }, "code": 2 }, "value": { "house": { "name": "dddd", "tags": [ "整租" ] } }, "property": "data", "children": [ { "target": { "house": { "name": "dddd", "tags": [ "整租" ] } }, "value": { "name": "dddd", "tags": [ "整租" ] }, "property": "house", "children": [ { "target": { "name": "dddd", "tags": [ "整租" ] }, "value": "dddd", "property": "name", "children": [], "constraints": { "length": "name的长度不能小于10不能大于20" } } ] } ] }, { "target": { "data": { "house": { "name": "dddd", "tags": [ "整租" ] } }, "code": 2 }, "value": 2, "property": "code", "children": [], "constraints": { "equals": "code must be equal to 0" } } ] 解释错误信息的内容 { target: Object; // 检验的对象 property: string; // 未通过的属性 value: any; // 检验未通过的值 constraints?: { //验证失败并带有的错误信息的约束 [type: string]: string; }; children?: ValidationError[]; // 该属性的所有嵌套的验证错误 } 结论 ValidationError数组中的报错信息,嵌套的层级可能会比较深,我们可以用递归算法将所需的信息取出。这也是class-validator不太理想的地方。

进一步提升用户体验

  1. 优雅的提示 如果错误无法避免。。。。。那就让它好看一点 非致命的错误,用默认数据填充一下,还可以展示页面 致命的错误,跳转到一个友好的错误提示页面
  2. 主动上报错误 被动等待用户或者产品反馈问题是一件很蠢的事情,这时候可能都已经过了无数的时间,影响了无数的用户了。作为一家崇尚用户第一的公司,我们不能允许这种情况出现, 第一个发现问题的人应该是开发人员,我们需要有主动发现问题和报警的能力 将项目接入前端监控系统 apm前端监控系统是监控前端报错信息并且将前端的报错以邮件的形式发送给开发人员,这样可以使开发人员 第一时间发现错误,并且及时解决。 主动上报错误信息 需要上报错误信息时,validate方法中,调用apm.report方法 validate(post).then(errors => { if (errors.length > 0) { window.apm.report({errmsg:"数据为空", errorData: JSON.stringify(errors)}) } else { console.log("成功啦!"); } });