身为一个前端开发,在开发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