前言
ts真的非常好用,虽然它功能庞大,各种类型体操搞得人晕头转向,但是实际开发过程中,我们大概只需要它的类型提示功能,它能避免一些非常基础的报错。接口请求,响应数据是从后端回来的,如果接口少,一个个手动定义并没有什么问题,但是如果足够多,加上后端时不时要调整,那改起来真的是非常折磨人,所以这个功能自动化才正确的选择。
pont
pont是阿里出的一个工具,它可以非常方便的把后端的swagger文档转成ts提示类型,不仅如此,它还支持添加模板,直接生成对应的接口请求,甚至能帮我们生成请求参数的默认基类。
Github:github.com/alibaba/pon...
安装
首先把pont安装到全局
js
pnpm add -g pont-engine
下载vscode插件
下载后右边就会多出一个桥的图标
配置
在根目录创建一个叫做pont-config.josn,具体配置可以看文档 github.com/alibaba/pon...
这里贴一下我的配置文档
json
{
"originUrl": "http://localhost:3002/api/admin/api-doc-json",
"outDir": "./src/network/api",
"originType": "SwaggerV3",
"templatePath": "./pontTemplate",
"prettierConfig": {
"singleQuote": true,
"semi": true,
"tabWidth": 2,
"useTabs": false,
"bracketSpacing": true,
"arrowParens": "avoid",
"printWidth": 120,
"vueIndentScriptAndStyle": true,
"htmlWhitespaceSensitivity": "ignore"
}
}
-
- originUrl: swagger文档的json数据
-
- outDir:生成的文件位置
-
- originType:swagger文档类型
-
- templatePath:生成的接口模板
-
- prettierConfig:prettier配置,可以对生成的代码进行格式化
接下来在根目录创建一个pontTemplate.ts的文件,文档中对于这个参数的介绍是
指定自定义代码生成器的路径(使用相对路径指定)。一旦指定,pont 将即刻生成一份默认的自定义代码生成器。自定义代码生成器是一份 ts 文件,通过覆盖默认的代码生成器,来自定义生成代码。默认的代码生成器包含两个类,一个负责管理目录结构,一个负责管理目录结构每个文件如何生成代码。自定义代码生成器通过继承这两个类(类型完美,可以查看提示和含义),覆盖对应的代码来达到自定义的目的。 github.com/alibaba/pon...
我的pontTemplate.ts
js
import {
Interface,
CodeGenerator,
Mod,
Property,
BaseClass,
Surrounding,
FileStructures as OriginFileStructures,
} from 'pont-engine';
export default class MyGenerator extends CodeGenerator {
getInterfaceContent(inter: Interface) {
const method = inter.method.toLowerCase();
let query = inter.getParamsCode();
const body = inter.getBodyParamsCode();
let params = '';
let path = inter.path;
// 函数参数
let argumentStr = '';
// axios参数
let optionsStr = '';
if (inter.path.includes('{')) {
path = inter.path.split('{')[0];
params = inter.path.split('{')[1].replace('}', '');
argumentStr += params + ': string';
}
const queryExist = query && !params && query.includes(':');
if (queryExist) {
optionsStr = 'params, ';
const interfaceName = inter.name.slice(0, 1).toUpperCase() + inter.name.slice(1).replace('Api', '') + 'ParamsDto';
query = query.replace('Params', interfaceName);
query = query.replace('class', 'interface');
argumentStr += (argumentStr.length ? ', ' : '') + 'params: ' + interfaceName;
}
if (body) {
argumentStr += (argumentStr.length ? ', ' : '') + 'data: ' + body;
optionsStr = 'data, ';
}
argumentStr += argumentStr.length ? ', ' : '';
return `
import http from '@/network';
import type { RequestOptions } from '@/network/http.interface';
${queryExist ? 'export ' + query : ''}
export function ${inter.name}(${argumentStr}options?: RequestOptions) {
return http.request<${inter.responseType}>({
method: '${method}',
url: '${path}' ${params ? '+ ' + params : ''},
${optionsStr ? optionsStr + '...options' : '...options'}
});
};
`;
}
/** 获取接口类和基类的总的 index 入口文件代码 */
getIndex() {
return `
import * as defs from './baseClass';
import * as mods from './mods';
export { defs, mods }
`;
}
/** 获取模块的 index 入口文件 */
getModIndex(mod: Mod) {
let exportPath = 'export { ';
const importPath = mod.interfaces.map((i: Interface, index: number) => {
if (index === 0) {
exportPath += i.name;
} else {
exportPath += ', ' + i.name;
}
return `import { ${i.name} } from './${i.name}';`;
});
return `
${importPath.join('\n')}
${exportPath} };
`;
}
/** 获取所有模块的 index 入口文件 */
getModsIndex() {
let exportPath = 'export { ';
const importPath = this.dataSource.mods.map((mod: Mod, index: number) => {
if (index === 0) {
exportPath += mod.name;
} else {
exportPath += ', ' + mod.name;
}
return `import * as ${mod.name} from './${mod.name}';`;
});
return `
${importPath.join('')}
${exportPath} };
`;
}
/** 实体类添加类型 */
getBaseClassesIndex(): string {
const classCodeList = this.dataSource.baseClasses.map((base: BaseClass) => {
return `
class ${base.name} {
${base.properties
.map((property: Property<any>) => {
let value =
property.toPropertyCode(Surrounding.typeScript, true).slice(0, -1) +
' = ' +
property.dataType.getInitialValue();
// 由于 pont 没有对 number 类型进行处理,初始值给了 undefined
// 所以这里需要将基类属性类型为number的初始值 改为 0
if (property.dataType.typeName === 'number') {
value = value.replace(/undefined/g, '0');
}
// 对可选值进行undefined处理
if (!property.required) {
value = value.replace(/=.+/, '= undefined');
}
return value;
})
.join('\n')}
}
`;
});
return classCodeList.map(classCode => `export ${classCode}`).join('\n');
}
}
export class FileStructures extends OriginFileStructures {
/** 获取 index 内容 */
getModsDeclaration() {
// 由于我们不使用 pont 的 request模板,所以这里我们只需要导出接口的定义
// API 的定义声明,这里不需要所以返回空
return '';
}
}
这里也贴一下生成的文件格式和内容
可以看到这里的请求参数和响应参数都有对应的类型存在了,defs定义在api.d.ts中。让我们回过头来看一下pontTemplate文件,我们可以知道getInterfaceContent作用是生成对应接口代码,如果你对接口请求的封装于我不一样,可以更改这里的模板。
使用
生成的基类默认值
使用的时候直接引用对应的方法,甚至可以直接引用基类作为默认值。
参考
pont文档:github.com/alibaba/pon... 文章:juejin.cn/post/713026...