博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
科学甩锅技术: Typescript 运行时数据校验
阅读量:6447 次
发布时间:2019-06-23

本文共 6419 字,大约阅读时间需要 21 分钟。

本文首发于

如文章对你有帮助, 是对我最大的支持

背景

大家出来写 Bug 代码的,难免会出 Bug。

文章背景就发生在一个 Bug 身上,

有一天,测试慌张中带着点兴奋冲过来: 测试:"xxx系统前端线上出 Bug 了,点进xx页面一片空白啊"。 我:"纳尼?我写的Bug怎么会出现代码呢?"。

虽然大脑一片空白,但是锅还是要背的。 进入页面一看,哦豁,完蛋,cannot read the property 'xx' of undefined。确实是前端常见的报错呀。

背锅王,我当定了?

NO!

我眉头一皱,发现事情并不是那么简单,经过一番猛如虎的操作之后,最终定位到问题是:后端接口响应的 JSON 数据中,一个嵌套比较深的字段没有返回,即前端只读到了 undefined

咱按章程办事,后端提供的接口文档指定了数据结构,那你没有返回正确数据结构,这就是你后端的锅,虽然严谨点前端也能捕获到错误进行处理,但归根到底,是你后端数据接口处理有问题,这锅,我不背。

甩锅又是一门扯皮的事情,杀敌一千自伤八百,锅已经扣下来了,想甩出去就难咯,。

唉,要是在接口出错的时候,能立刻知道接口数据出问题,先发制人,马上把锅甩出去那就好咯。

这就是本文即将要讲述的 "Typescript 运行时数据校验"。

为什么要运行时校验数据?

众所周知,TypescriptJavaScript 超集,可以给我们的项目代码提供静态类型检查,避免因为各种原因而未及时发现的代码错误,在编译时就能发现隐藏的代码隐患,从而提高代码质量。

但是,TypeScript 项目的一个常见问题是: 如何验证来自外部源的数据并将验证的数据与TypeScript类型联系起来。 即,如何避免后端 API 返回的数据与 Typescript 类型定义不一致导致的运行时错误。

Typescript 能用于运行时校验数据类型,那么有没有一种方法,能让我们在 运行时 也进行 Typescript 数据类型校验呢?

io-ts 解决方案?

业界开源了一个运行时校验的工具库:。

//  io-ts 例子import * as t from 'io-ts'// ts 定义interface Category {  name: string  categories: Array
}// 对应上述ts定义的 io-ts 实现const Category: t.Type
= t.recursion('Category', () => t.type({ name: t.string, categories: t.array(Category) }))复制代码

但是,如上面的代码所示,这工具看起来就有点啰嗦有点难用,对代码的侵入性非常强,要全盘依据它的语法来重写代码。这对于一个团队来说,存在一定的迁移成本。

而我们更希望做到的理想方案是:

写好接口的数据结构 typescript 定义,不需要做太多的额外变动,直接就能校验后端接口响应的数据结构是否符合 typescript 接口定义

理想方案探索

首先,我们了解到,后端响应的数据接口一般为 JSON,那么,抛开 Typescript,如果要校验一个 JSON 的数据结构,我们可以怎么做到呢?

答案是。

JSON schema

JSON schema 是一种描述 JSON 数据格式的模式。

例如 typescript 数据结构:

type TypeSex = 1 | 2 | 3interface UserInfo {    name: string    age?: number    sex: TypeSex}复制代码

等价于以下的 json schema :

{    "$id": "api",    "$schema": "http://json-schema.org/draft-07/schema#",    "definitions": {        "UserInfo": {            "properties": {                "age": {                    "type": "number"                },                "name": {                    "type": "string"                },                "sex": {                    "enum": [                        1,                        2,                        3                    ],                    "type": "number"                }            },            "required": [                "name",                "sex"            ],            "type": "object"        }    }}复制代码

根据已有 json-schema 校验库,即可校验数据对象

someValidateFunc(jsonSchema, apiResData)复制代码

这里大家可能就又会困惑:这json-schema写起来也太费劲了?还不一样要学习成本,那和 io-ts 有什么区别。

但是,既然我们同时知道 typescriptjson-schema 的语法定义规则,那么就两者必然能够互相转换。

也就是说,即便我们不懂 json-schema 的规范与语法,我们也能通过typescript 转化生成 json-schema

那么,在以上的前提下,我们的思路就是:既然 typescript 本身不支持运行时数据校验,那么我们可以将 typescript 先转化成 json schema, 然后用 json-schema 校验数据结构

typescript -> json-schema

要将 typescript 声明转换成 json-schema ,这里推荐使用 。

我们可以直接使用它的命令行工具,这里就不仔细展开说明了,感兴趣的可以看下官方文档:

Usage: typescript-json-schema 
Options: --refs Create shared ref definitions. [boolean] [default: true] --aliasRefs Create shared ref definitions for the type aliases. [boolean] [default: false] --topRef Create a top-level ref definition. [boolean] [default: false] --titles Creates titles in the output schema. [boolean] [default: false] --defaultProps Create default properties definitions. [boolean] [default: false] --noExtraProps Disable additional properties in objects by default. [boolean] [default: false] --propOrder Create property order definitions. [boolean] [default: false] --required Create required array for non-optional properties. [boolean] [default: false] --strictNullChecks Make values non-nullable by default. [boolean] [default: false] --useTypeOfKeyword Use `typeOf` keyword (https://goo.gl/DC6sni) for functions. [boolean] [default: false] --out, -o The output file, defaults to using stdout --validationKeywords Provide additional validation keywords to include [array] [default: []] --include Further limit tsconfig to include only matching files [array] [default: []] --ignoreErrors Generate even if the program has errors. [boolean] [default: false] --excludePrivate Exclude private members from the schema [boolean] [default: false] --uniqueNames Use unique names for type symbols. [boolean] [default: false] --rejectDateType Rejects Date fields in type definitions. [boolean] [default: false] --id Set schema id. [string] [default: ""]复制代码

github 上也有所有类型转换的 ,可以对比看看 typescript 和 转换出的 json-schema 结果

json-schema 校验库

利用 typescript-json-schema 工具生成了 json-schema 文件后,我们需要根据该文件进行数据校验。

json-schema 数据校验的库很多,, 之类的,这里用 jsonschema 作为示例。

import { Validator } from 'jsonschema'import schema from './json-schema.json'const v = new Validator()// 绑定schema,这里的 `api` 对应 json-schema.json 的 `$id`v.addSchema(schema, '/api') const validateResponseData = (data: any) => {  // 校验响应数据  const result = v.validate(data, {    // SomeInterface 为 ts 定义的接口    $ref: `api#/definitions/SomeInterface`  })  // 校验失败,数据不符合预期  if (!result.valid) {    console.log('data is ', data)    console.log('errors', result.errors.map((item) => item.toString()))  }  return data}复制代码

当我们校验以下数据时:

// 声明文件interface UserInfo {        name: string        sex: string         age: number        phone?: number    }// 校验结果validateResponseData({    name: 'xxxx',    age: 'age应该是数字'})// 得出结果// data is  { name: 'xxxx', age: 'age应该是数字' }// errors [ 'instance.age is not of a type(s) number',//   'instance requires property "sex"' ]复制代码

配合上前端上报系统,当线上系统接口返回了非预料的数据,导致出 bug,就可以实时知道到底错在哪了,并且及时甩锅给后端啦。

commit 时自动更新 json-schema

前面提到,我们需要执行 typescript-json-schema <path-to-typescript-files-or-tsconfig> <type> 命令来声明 typescript 对应的 json-schema 文件。

那么,这里就有个问题,接口数量有可能增加,接口数据也有可能变动,那也就代表着,我们每次变更接口数据结构,都要重新跑一下 typescript-json-schema ,时刻保持 json-schema 和 typescript一一对应。

这我们就可以用 的 precommit , 加上 来实现每次更新提交代码时,自动执行 typescript-json-schema,无需时刻关注 typescript 接口定义的变更。

总结

综上,我们实现了

  1. typescript 声明文件 转换生成 json-schema 文件
  2. 代码接口层拦截校验数据,如校验失败,通过前端上报系统(如:)进行相关上报
  3. 通过 husky + lint-staged 每次提交代码自动执行 步骤1,保持git 仓库的代码 typescript 声明 和 json-schema 时刻保持一致。

那么,当 Bug 出现的时候,你甚至可以在测试都还没发现这个 Bug之前,就已经把锅甩了出去。

只要你跑得足够快,Bug 就会追不上你。

转载地址:http://zutwo.baihongyu.com/

你可能感兴趣的文章
pythonl练习笔记——PythonNet 套接字socket
查看>>
JS处理数组内如果相同ID追加一个属性(如字体颜色)
查看>>
30个redis.conf 配置项说明
查看>>
shell的浮点运算
查看>>
npm命令
查看>>
linux中backticks反引号的作用
查看>>
关于c++中如何调整输出格式的讲解!!!
查看>>
【C++沉思录】代理类
查看>>
RDLC报表多页打印时的一些问题
查看>>
PHP 和 AJAX responseXML 实例
查看>>
js获取触发事件元素的坐标
查看>>
Django Model获取指定列的数据
查看>>
Delphi MaskEdit用法
查看>>
五款免费的Windows 8 Metro风格主题
查看>>
android:menu.xml
查看>>
RecyclerView 知识梳理(5) ItemTouchHelper
查看>>
干货分享:智慧工厂时代下大数据 + 智能的深度实践
查看>>
新功能:阿里云负载均衡SLB支持HTTP/HTTPS超时时间自定义功能
查看>>
跟着Zepto学dom(二)
查看>>
面试Tip:Android优化之APK瘦身
查看>>