0

使用TypeScript校验运行时数据

 2 years ago
source link: https://segmentfault.com/a/1190000040652111
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

对于前端程序猿,常见的开发流程是:

  1. 前后端约定接口
  2. 后端给出接口文档
  3. 根据接口编写 TypeScript 类型定义
  4. 开发完成进行联调

虽然一切顺利,但是上线后还是翻车了,js 报错:cannot read the property 'xx' of null,很显然前端没有处理空值,接锅吧 TT。但回头一看接口文档,跟后端同学约定的返回对象,但实际返了 null,这锅不能一个人背。那么怎样才能尽早发现这种问题呢?一种解决方案是:

  1. 将 TypeScript 类型定义转为 JSON Schema
  2. 利用 JSON Schema 校验数据正确性

针对以上方案做了一个 demo

JSON Schema

JSON Schema 是一个 JSON 对象,用来描述和校验 JSON 对象的格式。比如下面这个 JSON Schema:

{
  "type": "object",
  "properties": {
    "name": {
      "type": "string"
    },
    "age": {
      "type": "number"
    },
    "hobby": {
      "type": "array",
      "items": {
        "type": "string"
      }
    }
  },
  "required": [
    "age",
    "hobby",
    "name"
  ],
  "$schema": "http://json-schema.org/draft-07/schema#",
}

它描述了这样一个 JSON 对象:

  • 类型 - typeobeject
  • 有四个属性 - properties

    • name:类型为 string
    • age:类型为 number
    • hobby:类型为 Array<string>
  • 属性是否必须 - requirednameagehobby 都为必须

下面这个 JSON 对象就是满足这个 JSON Schema 的:

{
  "name": "Tom",
  "age": 1,
  "hobby": ["coding"]
}

可以看出 JSON Schema 还是很好理解的,但其描述语法还是有一定学习成本,这里强烈推荐通过 jsonschema 库的 example 去学习相关语法,另外也可以看 Understanding JSON Schema

有了 JSON Schema,怎么使用它校验 JSON 对象的合法性呢?这里就用到了刚刚提到的jsonschema 库。简单使用示例如下:

var Validator = require('jsonschema').Validator;
var v = new Validator();
var instance = 4;
var schema = {"type": "number"};
console.log(v.validate(instance, schema));

现在可以根据 JSON Schema 去校验后端返回数据的格式是否正确了,但是为每个接口手动编写 JSON Schema 是不现实的,我们自然会想到能不能将 TypeScript 的接口类型定义转为 JSON Schema?

TypeScript Interface -> JSON Schema

好在已经有 typescript-json-schema 这个库帮我们解决了这个问题,下面给出简单的使用示例:

import path from "path";
import * as TJS from "typescript-json-schema";

const settings: TJS.PartialArgs = {
  required: true
};

// optionally pass ts compiler options
const compilerOptions: TJS.CompilerOptions = {
  strictNullChecks: true
};

// 解析接口定义文件:index.ts
const program = TJS.getProgramFromFiles(
  [path.join(__dirname, './apis/index.ts')],
  compilerOptions,
);

// 将"IApi1"这个interface转为schema,传入"*"将转换全部interface
let schema = TJS.generateSchema(program, "IApi1", settings) || {};

一顿操作后就可以将下面这个 interface 转为文章开头给出的示例 JSON Schema:

interface IApi1 {
  name: string;
  age: number;
  hobby: string[];
}

然后再用 node 将刚刚得到的 schema 存成 json 文件:

fs.writeFileSync(path.join(__dirname, "./json-schema", "schema.json"), schema); 

接着就可以使用相应的 JSON Schema 对后端数据进行校验了:

import { Validator } from 'jsonschema'

const apiSchema = require('./json-schema/schema.json')
const v = new Validator();
Api1().then(res => { 
  const validateRes1 = v.validate(res, apiSchema)
  console.log(validateRes1);
});

完整的示例可以看:demo

工程中的实践

1、如何组织散落的接口类型定义?
我个人比较喜欢的方式是:在 apis.ts 文件中统一去写所有的接口定义和类型定义,这样在转换 JSON Schema 时去处理这一个文件即可。

2、怎样自动将类型定义转为 JSON Schema?
使用 husky,在 pre-commit 阶段去执行转换工作,进一步可以使用 lint-staged 判断当前提交是否涉及到接口定义文件的改动,有改动再执行转换。
关于脚本的编写:

  • 首先创建脚本

    touch scripts/transfer.js

    transfer.js 中去编写 TS 转为 JSON Schema 的逻辑

  • package.json 中添加 scripts

    "scripts": {
      "transfer": "node scripts/transfer.js"
    }

    或者在 pre-commit 中去执行该脚本。

3、何时校验数据?
这里我想到两个场景:

  • 生产上:对于一些关键接口,在接口返回数据后调用校验逻辑,如果校验有错,需要做两件事:第一是将错误数据转为正确的备用数据,以防页面挂掉;第二是上报错误;
  • 测试时:写各种测试用例去测后端接口,校验返回数据的正确性,这样就不用人眼去校验数据是否正确了。

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK