阅读 495

前端防御性编程终极方案

前言

我们这里的防御只谈如何防御后端传的数据本身有问题的情况,举个例子,你和后端约定好,我们的数据格式如下:

{
    data: { // data 要求是一个对象
        personInfo: { // personInfo 要求是一个对象
            name: '张三', // personInfo属性必须有name属性且为字符串
            age: 12, //  personInfo属性必须有age属性且为数字
        },
        ...XXX //其它属性省略
    },  
    success: 0, // 0 表示成功 1 表示失败
}
复制代码

我们前端如果要用后端数据,我相信绝大部分前端同事都会写这样的代码

// 模拟后端数据
const mockData = {
    data: { // data 要求是一个对象
        personInfo: { // personInfo 要求是一个对象
            name: '张三', // personInfo属性必须有name属性且为字符串
            age: 12, //  personInfo属性必须有age属性且为数字
        },
        ...XXX //其它属性省略
    },  
    success: 0, // 0 表示成功 1 表示失败
}
复制代码

业务使用

// 用来判断是否是对象
const isObj = (obj) => Object.prototype.toString.call(obj).toLowerCase() == "[object object]";


if(mockData && mockData.data && isObj(mockData.data) && mockData.data.personInfo && isObj(mockData.data.personInfo)){
    这里才可以放心的使用mockData.data.personInfo数据
}
复制代码

有些同学可以用es6的?.操作符简化上面的写法,如下:

if(mockData?.data?.personInfo && isObj(mockData.data) && isObj(mockData.data.personInfo)){
    这里才可以放心的使用mockData.data.personInfo数据
}
复制代码

看起来简洁了很多,但是我们防御来防御去,各种if判断,真的很烦,因为数据校验,应该在service层做,也就是我们请求数据的fetch库,或者axios的xhr库去解决,我们业务层加入了校验,本身就不符合职责单一原则。

解决思路

那么怎么让service层去做这个事呢,我们可以把每次请求的数据,在service层做一个校验,如果这个数据格式不满足我们和后端商量的格式,那么直接就抛错,不走后面的逻辑了。

也就是说我们要让后端返回的数据百分百满足要求,我们前端才能不做防御性的if判断。(这样完美把锅甩给后端了!重点!重点!重点!)

有人说可以用typescript来做,其实不行,typescript只能在编译阶段起作用,而且打包之后这些类型校验都消失了,咋可以让类型校验在打包后可以生效呢?

class-validator很香

最终我们采取的方案是使用一个叫做class-validator的库,这个库可以说是一个简化验证的依赖库, 这个必须结合typescript来用,因为要用到装饰器。

用法如下:

import {validate, ValidateNested,Equals, Length, IsNotEmpty, IsNumber} from "class-validator";

// 构造一个用户类型,也就是personInfo的信息
// 这个类既可以作为typescript里的类型校验,又可以给class-validator用
class User {
    @Length(10, 20,{message: 'name的长度不能小于10不能大于20'})
    @IsNotEmpty({message:'name 不能为空'})
    name: string;

    @IsNumber({message:'age字段必须是number类型'})
    age: number;
}

 class Data {
    @ValidateNested()
    user: User;

    constructor() {
        this.user = new User();
    }
}

 class ResponseData {
    @ValidateNested()
    data: Data;

    @Equals(0)
    success: number

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

let data = {
    data: {
        personInfo: {
            name: '张三', 
            age: 12,
        },
    },  
    success: 0, // 0 表示成功 1 表示失败
}
复制代码

最后验证数据即可

let post = new ResponseData();


validate(post).then(errors => {
    if (errors.length > 0) {
        console.log(errors);
    } else {
        console.log("成功啦!");
    }
})
复制代码

业务层就当做数据一定的对的去使用,后端数据防御性判断需要单独在一个层面去使用,相信会使代码更简洁,更符合单一原则.

文章分类
前端