您的位置:首页 > 其它

默认值+TS类型约束提高数据处理成功率

2020-06-30 09:53 756 查看

我们在处理数据时,常常会遇到某项数据,或者属性是

undefined
,从而引起中断性错误,造成数据处理失败。解决这一问题最直接的办法就是在使用前判断是否
undefined
,但是如果每一个数据使用前都进行判断,非常繁琐,而且容易遗漏。所以这里给大家介绍两个办法:

  • 利用默认值解决
    undefined
    ,避免繁琐的判断过程;
  • 利用 TypeScript 的类型系统对数据进行检查,避免遗漏。

为了更直观的描述问题和解决办法,我们设计了这样一个需求:

需求:后端响应数据转换成 UI 所需要的数据

一般来说,现在的前端组件对表格数据的要求,都是:

  • 以数组表示数据行,即当前页数据
  • 每行是一个数据对象,其属性对应于表格列
  • 除数据行之外,还包含一些附加信息用于分页,比如 库存数据总数(不是当前数据行的总数)
  • 每页数据呈现条数(一般由前端传给后端,后端返回数据时带出来)
  • 当前页数(一般由前端传给后端,后端返回数据时带出来)

那么,从后端返回的数据可能会是这样(JSON 示例)

{
"code": 0,
"message": "查询成功",
"data": {
"total": 12345,
"page": 3,
"rows": [
{
"id": 34,
"title": "第 34 号数据",
"stamp": "2020-06-25T20:18:19Z"
},
...
]
}
}

前端呈现如果使用 Layui 的数据表格,它所要求的数据格式是这样的(来自官方文档):

{
"code": 0,
"msg": "",
"count": 1000,
"data": [{}, {}]
}

远程响应数据结构和表格需要的数据结构之间,对应关系非常明显。

我们用

res
来表示后端 JSON 转成的 JavaScript 对象,那么为 Layui 准备的数据会这样取值:

const tableData = {
code: res.code,
msg: res.message,
count: res.data.total,
data: res.data.rows
}

这个处理转换非常简单,只是做了一个属性名称的变化。

但是远端返回的数据,可能没有

code
,或者
message
,甚至没有
data
,那么上面的处理结果就可能包括
undefined
。前端组件不希望有这样的值,所以需要添加默认值处理。

利用解构变量可以赋予默认值这一特性

为了演示默认值处理,我们假设,后端返回的数据规范比较灵活,为了节约网络资源,有一个默认值约定:

  • 如果请求正常完成,省略
    "code": 0
  • 如果没有特殊消息,省略
    "message": ""
  • 如果没有数据,即
    "total": 0
    的情况,省略
    "data": {}
  • 如果当前页没有数据,省略
    "rows": []

这种情况下,在进行数据转换时就需要充分考虑到某项数据可能不存在,避免

undefined
错误。

Object.assign()
或者 Spread 运算符可以部分解决这个问题,但是

  • 只能解决单层(直接)属性,不能解决深层属性默认值
  • 有坑,它们对 “
    undefiend
    属性”和“不存在的属性”处理行为不同

不过我们可以利用解构变量能够赋予默认值的特性来进行处理。下面这段代码就巧妙地利用了这一特性来避免

undefined
错误。

function getData(res) {
const { code = 0, message: msg, data = {} } = res;
const { total: count = 0, rows = [] } = data;

return {
code,
msg,
count,
data: rows
}
}

const tableData = getData(res);

解构确实是可以解决问题,但是如果遗漏或者写错属性,调试起来恐怕不易。比如上面

const { message: msg } = res;

就很容易错写成

const { msg } = res

这是 JS 的硬伤,即使用 ESLint 这样强悍的静态检查工具也不能解决。但是如果引入强类型定义,使用 TypeScript 就好办了。

使用 TypeScript 和类型检查转换过程

既然用 TypeScript,就需要为两个数据结构定义对应的类型了。

我们有两个数据结构

res
tableData
,在 TypeScript 里可以直接把它们定义为
any
类型,这是最简单的操作,但是没什么用 —— 因为 TypeScript 不检查
any
类型。

所以先根据我们之前的约定,把

res
tableData
的类型定义出来:

interface FetchData<T> {
total: number;
page?: number;
rows?: T[];
}

interface FetchResult<T> {
code?: number;
message?: string;
data?: FetchData<T>;
}

interface LayuiTalbeData<T> {
code: number;
msg?: string;
count: number;
data: T[];
}

然后要把

res
声明成
FetchResult&lt;T&gt;
类型。这里我们暂时不关心具体每行数据的结构,所以直接用
any
代替好了

const res: FetchResult<any> = await fetch();
// 或者
// const res = await fetch() as FetchResult<any>;

这种情况下,假如我们不小心写错了属性名,比如解构时把源属性

message
错写成了目录属性名
msg
,即
const { msg } = res
,VSCode 是会提示错误的:

解决的办法是使用解构重命名:

const { message: msg } = res;

或者,如果我们忘了处理

undefined
,比如忘了给解构的
rows
赋予初始值,那也会得到错误提示,因为

  • 源数据定义中
    rows?: T[]
    表示它可省略,即可能是
    undefined
  • 目标数据定义中
    data: T[]
    表示它一定不会是
    undefined

解决的办法是,赋予初始值,使其不可能为

undefined

const { rows = [] } = data;

完整的 TypeScript 代码如下(类型定义参考前面的定义):

function getData<T>(res: FetchResult<T>): LayuiTalbeData<T> {
const { code = 0, message: msg, data = {} as FetchData<T> } = res;
const { total: count = 0, rows = [] } = data;

return {
code,
msg,
count,
data: rows
}
}

// 这里 TypeScript 可以推导出 tableData 类型是 LayuiTalbeData<any>
const tableData = getData(res);

使用 Optional Chain 和 Nullish Coalescing

回到 JavaScript,其实还有一个办法可以处理默认值的问题:

const tableData = {
code: res.code || 0,
msg: res.message || "",
count: (res.data && res.data.total) || 0,
data: (res.data && res.data.rows) || []
}

这也是一个非常常见的办法。这个办法在 TypeScript 中配置类型定义同样可行。只是对多层属性的处理仍然显得有点麻烦。不过 JavaScript 最近引入了“Optional Chain”和“Nullish Coalescing”特性,这个代码可以更简洁:

const tableData = {
code: res.code ?? 0,
msg: res.message,
count: res.data?.total ?? 0,
data: res.data?.rows ?? []
};

放在 TypeScript 中是这样写的:

const tableData: LayuiTalbeData<any> = {
code: res.code ?? 0,
msg: res.message,
count: res.data?.total ?? 0,
data: res.data?.rows ?? []
}

上面的 TypeScript 代码中,如果错写了

res.msg
或者忘了加
?? []
等,TSC 或者 VSCode 都会有错误提示,以保证你能修正代码。

看,新特性配合 TypeScript 的强类型检查,简直是完美!

小结

我们讲了最简单的数据转换:直接按源数据属性取值。虽然简单,但是有坑,也有处理的方法和技巧:

  • 注意可能出现的
    undefined
    null
    ,甚至
    NaN
    等需要特殊处理的数据
  • 使用解构将属性提取出来,并根据数据结构的需要适当赋予初始值
  • 使用 Optional Chain 和 Nullish Coalescing 简化对可能为
    undefined
    属性的处理
  • 使用 TypeScript 在开发期检查错误

这里讲的数据处理比较基础,但其中的坑也比较容易被忽略。后面在专栏或订阅号中,我们还会继续探讨更复杂一些的数据处理分析方法和处理技巧,请关注!

喜欢此文,点个赞 ⇙

支持作者,**赏个咖啡豆 ⇓

内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: