之前的文章介绍了使用 yapi-to-typescript (下文简称 ytt)生成接口类型定义文件,方便我们直接使用接口的请求和响应类型,能减少很多写接口类型的时间。

使用 yapi-to-typescript 生成接口响应数据的 TS 类型声明

既然能生成接口类型定义文件,那也就可以生成接口请求代码文件咯,可是网上找了下没找到有相关的工具库,想必是每个公司的项目接口请求代码风格差异较大,要开发一个可以灵活配置生成对应代码的库也略显麻烦,比如这篇文章就是教大家如何写出生成请求代码的文件,而我也没有写一个这样的库。

前置条件

首先需要了解两个工具库,一个是 ytt,一个是 shelljs。因为 ytt 已经帮我们请求 yapi 拿到了接口数据,我们就没有必要再去获取一次了。

shelljs 是一个具有 shell 功能的工具库,很强大,这里主要用来操作文件系统。

开搞

第一步:存储接口数据

ytt 中提供了钩子函数,在类型文件生成完成后会调用,但是这个函数中我们拿不到 yapi 上的接口,所以需要在 ytt 的 outputFilePath 中手动保存下来。

首先,我们定义两个变量和一个方法:

// 所有接口信息
let interfaceInfos: MyInterface[] = [];
// 所有生成的文件名。方便每次生成的时候先删除
let filesName = new Set<string>();
// 存储所有接口信息。在类型定义文件生成成功后的 success 钩子函数中遍历生成接口调用文件。
const saveInterface = (interfaceInfo: Interface, name: string) => {
filesName.add(name);
interfaceInfo.fileName = name; // 接口数据中增加 fileName 字段,告知这个接口属于哪个文件
interfaceInfos.push(interfaceInfo as MyInterface);
};

在 outputFilePath 将所有接口保存下来:

      outputFilePath: (interfaceInfo: Interface) => {
// 接口文档中分类有中文,使用拼音库转成英文拼音
let nameArr: string[] = pinyin(interfaceInfo._category.name, {
toneType: 'none',
nonZh: 'consecutive',
type: 'array',
});
let name: string = camelCase(nameArr.join('-')); // 这个函数就是保存所有接口信息的函数
saveInterface(interfaceInfo, name); return `src/types/api/${name}.ts`;
},

在 ytt 的 success 钩子函数中我们就可以编写生成请求代码的代码了:

  // 这里是 ytt 完成的钩子函数
{
success() {
shelljs.echo('--------------------接口请求代码生成中...');
// 先删除文件,每次重新生成
filesName.forEach((fileName) => {
shelljs.rm('-f', getFilePath(fileName));
});
// 遍历接口信息生成接口代码
interfaceInfos.forEach((item) => {
generatorRequest(item);
});
shelljs.echo('--------------------接口请求代码生成完成');
},
}

生成接口代码

生成接口代码有几点要注意:

  1. 接口文件只能生成一次,在第一次生成的时候往里面写入公共代码,如 import 语句。
  2. 在遍历每个接口数据的时候,可以通过上面往接口数据中添加的 fileName 字段知道这个接口属于哪个文件。
  3. 对 rest 风格地址的处理。

入口函数:

// 生成接口请求代码
const generatorRequest = (data: MyInterface): void => {
const filePath = getFilePath(data.fileName);
writeCommon(data.fileName, filePath);
const fnName = getFnName(data);
const [reqType, resType] = getReqResType(fnName);
const str = `
export const ${fnName} = (${getRest(data)}data?: Types.${reqType}, options?: Options) => {
return ${getMethod(data)}<Types.${resType}>(
\`${getReqPath(data)}\`,
data,
options
);
};
`;
shelljs.ShellString(str).toEnd(filePath);
};

封装 getFilePath 获取操作的文件路径,我把所有文件写在 src/services 中。

type MyInterface = Interface & { fileName: string };

// 生成文件目录路径
const basePath = './src/services'; // 获取编辑的文件的相对路径
export const getFilePath = (fileName: string): string => {
return `${basePath}/${fileName}.ts`;
};

写入公共内容:

// 写入公共内容,如 import 的依赖,只写一次
const writeCommon = (fileName: string, filePath: string): void => {
// 使用 shelljs.find 方法判断文件是否存在,从而一些公共内容在首次创建时写入
const res = shelljs.find(filePath);
// 0 是找到了,1 是没找到
if (res.code === 1) {
// 写入 import 代码
shelljs
.ShellString(
`
import * as Types from '@/types/api/${fileName}';
import request, { Options } from '@/utils/request';
`
)
.to(filePath);
}
};

写入公共代码后,generatorRequest 中以下的代码就是生成每条请求语句,追加到对应的文件中:

  const fnName = getFnName(data);
const [reqType, resType] = getReqResType(fnName);
const str = `
export const ${fnName} = (${getRest(data)}data?: Types.${reqType}, options?: Options) => {
return ${getMethod(data)}<Types.${resType}>(
\`${getReqPath(data)}\`,
data,
options
);
};
`;
shelljs.ShellString(str).toEnd(filePath);

通过请求地址生成请求的函数名:

// 生成函数名
const getFnName = (data: Interface): string => {
const { path } = data;
return camelCase(path);
};

由于 ytt 生成的类型名称也是通过路径生成的,所以我们将路径生成的函数名再稍加处理就可以生成请求和响应的类型名称了

// 生成请求和响应类型的字符串。路径生成的函数名首字符大写,然后加 Request 或 Response
const getReqResType = (fnName: string): string[] => {
const name = fnName.charAt(0).toUpperCase() + fnName.slice(1);
return [`${name}Request`, `${name}Response`];
};

最后一点要注意的是,由于项目中一些请求路径是 rest 风格的,需要对其进行处理。如 service/getUserInfo/:uid 这样的请求地址。

这里我通过正则匹配替换,将 :uid 替换成 ${rest.uid} 这样的字符串。然后函数中添加一个参数 rest。当然,也可以直接生成 ${data.uid},只是这样又得去给请求参数的类型添加联合类型了。

// 获取请求地址
const getReqPath = (data: Interface): string => {
let path = data.path;
if (path.startsWith('/')) {
path = path.substring(1);
}
path = convertReqPath(path);
return path;
}; // 转换请求地址中的 :xxx 为 ${rest.xxx}
const convertReqPath = (path: string): string => {
return path.replace(/:\w+/, (str) => {
return `\${rest.${str.slice(1)}}`;
});
};

最后在写一个函数生成请求函数中的 rest 参数:

// 请求 url 中有 :xxx 的时候,添加第一个参数 rest
const getRest = (data: Interface): string => {
if (/:\w+/.test(data.path)) {
return 'rest: Record<string, string>, ';
} else {
return '';
}
};

以上这些函数就用在了这个模板字符串中:

  const str = `
export const ${fnName} = (${getRest(data)}data?: Types.${reqType}, options?: Options) => {
return ${getMethod(data)}<Types.${resType}>(
\`${getReqPath(data)}\`,
data,
options
);
};
`;

大功告成,运行 ytt 生成接口类型文件的同时,也生成了这样的请求文件:

前端工程化:使用 shelljs 生成 yapi 接口文件的更多相关文章

  1. 基于webpack的前端工程化开发解决方案探索(一):动态生成HTML(转)

    1.什么是工程化开发 软件工程的工程化开发概念由来已久,但对于前端开发来说,我们没有像VS或者eclipse这样量身打造的IDE,因为在大多数人眼中,前端代码无需编译,因此只要一个浏览器来运行调试就行 ...

  2. 基于gulp编写的一个简单实用的前端开发环境好了,安装完Gulp后,接下来是你大展身手的时候了,在你自己的电脑上面随便哪个地方建一个目录,打开命令行,然后进入创建好的目录里面,开始撸代码,关于生成的json文件请点击这里https://docs.npmjs.com/files/package.json,打开的速度看你的网速了注意:以下是为了演示 ,我建的一个目录结构,你自己可以根据项目需求自己建目

    自从Node.js出现以来,基于其的前端开发的工具框架也越来越多了,从Grunt到Gulp再到现在很火的WebPack,所有的这些新的东西的出现都极大的解放了我们在前端领域的开发,作为一个在前端领域里 ...

  3. vuex前端工程化之动态导入文件--require.context( )

    随着项目的复杂,文件结构越来越多,Store中modules中的文件也越来越多,如果一个一个加载是不是很麻烦呢? 先看一个项目中store项目结构: 过去都是通过import分别引入文件: 1 imp ...

  4. 前端工程化(二)---webpack配置

    导航 前端工程化(一)---工程基础目录搭建 前端工程化(二)---webpack配置 前端工程化(三)---Vue的开发模式 前端工程化(四)---helloWord 继续上一遍的配置,本节主要记录 ...

  5. 页面仔初窥"前端工程化"

    今天看了几篇前端界的一位大牛--张云龙的文章,其中一篇在自己的理解范围内看得懂一些,有所收获,说的是前端工程化的事,看完算是对前端工程形成了一个模糊的概念. 现在我所接触到的前端开发,还是张云龙大神所 ...

  6. 公司内部技术分享之Vue.js和前端工程化

    今天主要的核心话题是Vue.js和前端工程化.我将结合我这两年多的工作学习经历来谈谈这个,主要侧重点是前端工程化,Vue.js侧重点相对前端工程化,比重不是特别大. Vue.js Vue.js和Rea ...

  7. 10分钟学会前端工程化(webpack4.0)

    一.概要 1.1.前端工程化 随着前端的不断发展与壮大,前端变得越来越复杂,组件化.模块化.工程化.自动化成了前端发展中不可或缺的一部分,具体到前端工程化,面临的问题是如何提高编码->测试-&g ...

  8. web前端工程化/构建自动化

    前端工程化 前端工程化的概念在近些年来逐渐成为主流构建大型web应用不可或缺的一部分,在此我通过以下这三方面总结一下自己的理解. 为什么需要前端工程化. 前端工程化的演化. 怎么实现前端工程化. 为什 ...

  9. chrome插件: yapi 接口TypeScript代码生成器

    前言 2020-09-12 天气晴,蓝天白云,微风,甚好. 前端Jser一枚,在公司的电脑前,浏览器打开着yapi的接口文档,那密密麻麻的接口数据,要一个一个的去敲打成为TypeScript的inte ...

随机推荐

  1. 测试开发实战[提测平台]20-图表G2Plot在项目的实践实录

    微信搜索[大奇测试开],关注这个坚持分享测试开发干货的家伙. G2Plot项目应用 上一篇<提测平台19-Echarts图表在项目的实践>讲解了Echarts的图表应用,此篇来看下开箱即用 ...

  2. 计算机网络-5-10-TCP运输连接管理

    TCP的运输连接管理 TCP是面向连接的通信,运输连接是用来传送TCP报文的,TCP运输连接的建立和释放是每一次面向连接的通信中必不可少的过程.因此,运输连接有三个阶段,即:建立连接,数据传送,连接释 ...

  3. nested exception is java.lang.NoClassDefFoundError: org/fusesource/hawtbuf/UTF8Buffer

    前言:IDE管理maven项目,总是遇到各种莫名奇妙的问题,有的是导入了依赖,IDE确报包未找到,有的是IDE显示找到,但是控制台确报未找到,有以下几种方法可以解决 第一:确认自己导入的依赖是否有问题 ...

  4. 浅谈AngularJS中使用$resource

    这个服务可以创建一个资源对象,我们可以用它非常方便地同支持RESTful的服务端数据源进行交互,当同支持RESTful的数据模型一起工作时,它就派上用场了. REST是Representational ...

  5. 4G无线全网通太阳能水文设备电源监测系统BMS110

    钡铼技术BMS110模块可实现4路电池电压.2路模拟量.2路数字量和1路温度测量,支持Modbus RTU over TCP和MQTT通讯协议,DC9-36V电源供电.BMS110可应用于各种有使用蓄 ...

  6. 05.python语法入门--垃圾回收机制

    # (1)垃圾回收机制GC# 引用计数# x = 10 # 值10引用计数为1# y = x   # 值10引用计数为2## y = 1000 # 值10引用计数减少为1# del x     # 值 ...

  7. 布尔值与比较运算符"=="

    $man = "男";$flag = $man == "男"; //双等号是比较运算符,返回布尔值,成立则返1赋给$flag 不成立返回0,0即为nullech ...

  8. 花里胡哨之自定义linux终端前缀显示

    文章目录 1.先看默认的linux终端前缀 2.查看默认的终端前缀变量 3.符号所代表的意义 4.修改PS1变量,达成自定义效果 4.1.只显示主机名和完整目录 4.2.给他点颜色看看 5.谢幕 1. ...

  9. Cobbler 批量安装操作系统

    文章目录 环境准备 部署cobbler cobbler语法检查以及排错 问题1 问题2 问题3 问题4 问题5 问题6 问题7 问题8 修改dhcp模板 重启服务,再次检查 镜像配置 镜像导入 kic ...

  10. c++ 文本处理

    c++ 文本处理 1.使用sstream版本 (1)功能:截取第一列为1以后的数据,如下图,截取第5行(包括第5行)以后的数据,前面4行数据丢弃. (2)代码:textProc.cc #include ...