你还在手写TS类型代码吗

更新日期: 2022-02-15阅读: 588标签: 代码

身为一个前端开发,在开发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数据等简单可复制代码的思路,实现后能减少一部分劳动密集型的工作内容,让我们更专注于业务逻辑开发。

来自:https://segmentfault.com/a/1190000041399444


链接: https://www.fly63.com/article/detial/11167

不要浪费时间写完美代码

一个系统可以维持5年,10年,甚至20年以上,但是代码和设计模式的生命周期非常短,当对一个解决方案使用不同的方法进行迭代的时候,通常只能维持数月,数日,甚至几分钟的时间

Google内部在代码质量上的实践

良好的编程习惯涉及到很多方面,但在软件行业内,大多数的公司或组织都不会把良好的编程习惯列为主要关注点。 例如,具有可读性和可维护性的代码比编写好的测试代码或使用正确的工具更有意义,前者的意义在于可以让代码更易于理解和修改。

减少嵌套,降低代码复杂度

减少嵌套会让代码可读性更好,同时也能更容易的找出bug,开发人员可以更快的迭代,程序也会越来越稳定。简化代码,让编程更轻松!

关于 Google 发布的 JS 代码规范

Google为了那些还不熟悉代码规范的人发布了一个JS代码规范。其中列出了编写简洁易懂的代码所应该做的最佳实践。代码规范并不是一种编写正确JavaScript代码的规则,而是为了保持源代码编写模式一致的一种选择。

你解决的问题比你编写的代码更重要!

程序员似乎忘记了软件的真正目的,那就是解决现实问题。您编写的代码的目的是为了创造价值并使现有世界变得更美好,而不是满足您对自我世界应该是什么的以自我为中心的观点。有人说:如果你拥有的只是一把锤子,那么一切看起来都像钉子一样

tinymce与prism代码高亮实现及汉化的配置

TinyMCE是一个轻量级的基于浏览器的所见即所得编辑器,由JavaScript写成。它对IE6+和Firefox1.5+都有着非常良好的支持。功能方强大,并且功能配置灵活简单。另一特点是加载速度非常快的。

js函数式编程与代码执行效率

函数式编程对应的是命令式编程, 函数式编程的核心当然是对函数的运用. 而高阶函数(Higher-order)是实现函数式编程的基本要素。高阶函数可以将其他函数作为参数或者返回结果。所以JS天生就支持函数式编程

接手代码太烂,要不要辞职?

朋友发表了一条说说:入职新公司,从重构代码到放弃”,我就问他怎么了?他说,刚进一家新公司,接手代码太烂,领导让我先熟悉业务逻辑,然后去修复之前项目中遗留的bug,实在不行就重构

js高亮显示关键词_页面、搜索关键词高亮显示

页面实现关键词高亮显示:在项目期间遇到一个需求,就是搜索关键词时需要高亮显示,主要通过正则匹配来实现页面关键词高亮显示。在搜索结果中高亮显示关键词:有一组关键词数组,在数组中筛选出符合关键字的内容并将关键字高亮

写优雅的代码,做优雅的程序员

软件工程学什么? 学计算机,写程序,做软件,当程序员。听说学计算机很辛苦? 是的,IT行业加班现象严重。在计算机世界里,技术日新月异,自学能力是程序员最重要的能力之一。选了这个专业,就要时刻保持好奇心和技术嗅觉,不能只满足于完成课内作业。

点击更多...

内容以共享、参考、研究为目的,不存在任何商业目的。其版权属原作者所有,如有侵权或违规,请与小编联系!情况属实本人将予以删除!