乐趣区

关于javascript:你还在手写TS类型代码吗

身为一个前端开发,在开发 ts 我的项目时,最繁琐的工作应该就是手写接口的数据类型和 mock 数据,因为这部分工作如果不做,前面写业务逻辑好受,做的话全是复制粘贴相似的反复工作,还挺费时间。下文将给大家介绍一个主动生成 ts 类型和 mock 数据的办法,帮忙同学们从繁琐得工作中解脱进去。

上面咱们将通过一个示例,让大家一起理解一下代码生成的根本过程。

TS 代码生成根本流程

咱们以上面这段 ts 代码为例,一起过一下生成它的根本流程。

export interface TestA {
  age: number;
  name: string;
  other?: boolean;
  friends: {sex?: 1 | 2;},
  cats: number[];}

第一步:选定数据源

咱们先思考一个问题,把上述代码写的 interface 生成须要哪些信息?

通过剖析,咱们首先须要晓得它一共有几个属性,而后要晓得哪些属性是必须的,除此以外还须要晓得每个属性的类型、枚举等信息。有一种数据格式能够完满的给咱们提供咱们所须要的数据,它就是JSON Schema

接触过后端的同学应该都理解过JSON Schema,它是对 JSON 数据的形容,举个例子,咱们定义了上面这个 JSON 构造:

{
  "age": 1,
  "name": "测试",
  "friends": {"sex": 1},
  "cats": [
    1,
    2,
    3
  ],
  "other": true
}

咱们口头形容下这个 json:它有 age、name、friends、cats、other5 个属性,age 属性的类型是 number,name 属性的类型是 string,cats 属性的类型是 number 组成的 arry,friends 属性是一个 object,它有一个 sex 属性,类型是数字,other 属性的类型是 boolean。

用 JSON Schema 的形容如下:

{
  "type": "object",
  "properties": {
    "age": {"type": "number"},
    "name": {"type": "string"},
    "cats": {
      "type": "array",
      "items": {"type": "number"}
    },
    "friends": {
      "type": "object",
      "properties": {
        "sex": {"type": "number"},
        "required": ["e"]
      }
    },
    "other": {"type": "boolean",},
    "required": [
      "a",
      "b"
    ]
  }
}

能够看出 JSON Schema 能够完满的程序化实现咱们的口头形容,这个例子比较简单,JSON Schema的形容能力远不止于此,比方 枚举,数组的最大长度,数字的最大最小值,是否是必须的 等咱们罕用的属性都能准确形容,所以它也罕用于用户输出校验的场景。

第二步:选定代码生成工具

看到这个题目,置信大多数同学都曾经晓得了答案,没错,就是 TS AST 和 TS Compiler API,后者能够生成或者批改 TS AST,也能够输入编译后的文件。咱们来看一下如何应用TS Compiler API 生成形象语法树并且编译成上文中提的代码。

对应的 TS Compiler 代码如下:

factory.createInterfaceDeclaration(
    undefined,
    [factory.createModifier(ts.SyntaxKind.ExportKeyword)],
    factory.createIdentifier("TestA"),
    undefined,
    undefined,
    [
      factory.createPropertySignature(
        undefined,
        factory.createIdentifier("age"),
        undefined,
        factory.createKeywordTypeNode(ts.SyntaxKind.NumberKeyword)
      ),
      factory.createPropertySignature(
        undefined,
        factory.createIdentifier("name"),
        undefined,
        factory.createKeywordTypeNode(ts.SyntaxKind.StringKeyword)
      ),
      factory.createPropertySignature(
        undefined,
        factory.createIdentifier("other"),
        factory.createToken(ts.SyntaxKind.QuestionToken),
        factory.createKeywordTypeNode(ts.SyntaxKind.BooleanKeyword)
      ),
      factory.createPropertySignature(
        undefined,
        factory.createIdentifier("friends"),
        undefined,
        factory.createTypeLiteralNode([factory.createPropertySignature(
          undefined,
          factory.createIdentifier("sex"),
          factory.createToken(ts.SyntaxKind.QuestionToken),
          factory.createUnionTypeNode([factory.createLiteralTypeNode(factory.createNumericLiteral("1")),
            factory.createLiteralTypeNode(factory.createNumericLiteral("2"))
          ])
        )])
      ),
      factory.createPropertySignature(
        undefined,
        factory.createIdentifier("cats"),
        undefined,
        factory.createArrayTypeNode(factory.createKeywordTypeNode(ts.SyntaxKind.NumberKeyword))
      )
    ]
 )

乍一看生成这段简略类型的代码非常复杂,然而认真一看如果这些办法通过封装,代码会简洁不少,而且目前曾经有一些比拟成熟的第三方库库,比方 ts-morph 等。

Ts Compiler Api 只有英文文档,而且应用简单,而且生成不同类型的代码须要调用哪个函数咱们不好确定,但咱们能够去 TS AST View 查问,它能依据你输出的 TS 代码生成对应的形象语法树和 Compiler 代码,上述代码就是 TS AST View 提供的。

factory.createInterfaceDeclaration办法会生成一个 interface 节点,生成之后,咱们还须要调用一个办法将生成的 interface 打印进去,输入成字符串文件,参考代码如下:

// ast 转代码
// 须要将上文 factory.createInterfaceDeclaration 生成的节点传入
export const genCode = (node: ts.Node, fileName: string) => {const printer = ts.createPrinter();
    const resultFile = ts.createSourceFile(fileName, '', ts.ScriptTarget.Latest, false, ts.ScriptKind.TS);
    const result = printer.printNode(
        ts.EmitHint.Unspecified,
        node,
        resultFile
    );
    return result;
};

第三步:丑化输入的代码

丑化代码这一步咱们应该很相熟了,置信咱们编译器中都装有 Prettier,每个前端我的项目必备的工具,它不仅能够间接格式化咱们正在编写的文件,也能够格式化咱们手动传入的字符串代码,话不多说,上代码:

import * as prettier from 'prettier';

// 默认的 prettier 配置
const defaultPrettierOptions = {
    singleQuote: true,
    trailingComma: 'all',
    printWidth: 120,
    tabWidth: 2,
    proseWrap: 'always',
    endOfLine: 'lf',
    bracketSpacing: false,
    arrowFunctionParentheses: 'avoid',
    overrides: [
        {
            files: '.prettierrc',
            options: {parser: 'json',},
        },
        {
            files: 'document.ejs',
            options: {parser: 'html',},
        },
    ],
};

// 格式化丑化文件
type prettierFileType = (content:string) => [string, boolean];
export const prettierFile: prettierFileType = (content:string) => {
    let result = content;
    let hasError = false;
    try {
        result = prettier.format(content, {
            parser: 'typescript',
            ...defaultPrettierOptions
        });
    }
    catch (error) {hasError = true;}
    return [result, hasError];
};

第四步:将生成的代码写入咱们的文件

这一步比较简单,间接是有 node 提供的 fs Api 生成文件即可,代码如下:

// 创立目录
export const mkdir = (dir:string) => {if (!fs.existsSync(dir)) {mkdir(path.dirname(dir));
        fs.mkdirSync(dir);
    }
};
// 写文件
export const writeFile = (folderPath:string, fileName:string, content:string) => {const filePath = path.join(folderPath, fileName);
    mkdir(path.dirname(filePath));
    const [prettierContent, hasError] = prettierFile(content);
    fs.writeFileSync(filePath, prettierContent, {encoding: 'utf8',});
    return hasError;
};

前后端的协同

下面的流程还短少重要的一步:数据源 JSON Schema 谁提供?

这就须要前后端的协同,目前后端曾经有了很成熟的生成 JSON Schema 的工具,比方 Swagger,YAPI 等。

接入 Swagger 的后端系我的项目都能给前端提供 swagger.json 文件,文件的内容就包含所有接口的具体数据,包含 JSON Schema 数据。

YAPI 和 Swagger 不同,它是 API 的集中管理平台,在它下面治理的 api 咱们都能够通过它提供的接口获取的所有 api 的具体数据,和 swagger.json 提供的内容大同小异,而且 YAPI 平台反对导入或者生成 swagger.json。

如果有了接口治理平台和制订了相干标准,前后端的合作效率会晋升很多,缩小沟通老本,而且前端也能够基于治理平台做一些工程效力相干的工作

难点攻克

上述步骤只是简略的介绍了一下生成 ts 类型代码的一个思路,这思路下还有有一些难点须要解决的,比方:

  • 理论开发中咱们须要正文,但 TS Compiler API 不能生成正文:这个问题咱们能够通过再代码的 string 生成之后而后在对应的中央手动插入正文的形式解决
  • 理论业务的类型可能非常复杂,嵌套档次很深:这个问题咱们能够通过递归函数来解决
  • 曾经生成的类型代码,如果 API 有改变,应该怎么办,或者新增的 API 要和原来生成的放的一个文件下,这种状况怎么解决?TS ComPiler API 是能够读取源文件的,就是曾经存在的文件也是能够读取的,咱们能够读取源文件而后再利用 Compiler API 批改它的形象语法树实现批改或者追加类型的性能
  • 前后端的协同问题:这个就须要找 leader 解决了。

总结

通过下面提到的四个步骤,咱们理解了生成代码的根本流程,而且每一步的实现计划不是固定的,能够自行抉择:

  • 在数据源抉择的问题上,咱们除了 JSON Schema 还能够抉择原始的 json 数据当作数据源,只是生成的类型不是那么精准,在这举荐一个很好用的网站:JSON2TS。
  • 代码生成工具咱们也能够用罕用的一些模板引擎来生成,比方 Nunjucks,EJS 等,它们不仅能够生成 HTML,也能够 生成任何格局的文件,并且可能返回生成的字符串。
  • 代码丑化这步还是举荐应用 prettier。
  • 对于前端来说,目前最好的输入文件的形式就是 Node 了。

本文只提供了一种工程化生成 TS 类型、Mock 数据等简略可复制代码的思路,实现后能缩小一部分 劳动密集型 的工作内容,让咱们更专一于业务逻辑开发。

参考文献

  • TS Compiler API
  • Prettier
退出移动版