之前的文章介绍了使用 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. CKKS Part2: CKKS的编码和解码

    该文章翻译自CKKS EXPLAINED, PART 2: FULL ENCODING AND DECODING,主要介绍CKKS方案中是如何编码和解码的(如何将复数向量转成整数多项式,以及如何求逆运 ...

  2. Python标准库:datetime 时间和日期模块 —— 时间的获取和操作详解

    datetime 时间和日期模块 datetime 模块提供了以简单和复杂的方式操作日期和时间的类.虽然支持日期和时间算法,但实现的重点是有效的成员提取以进行输出格式化和操作.该模块还支持可感知时区的 ...

  3. salesforce零基础学习(一百一十一)custom metadata type数据获取方式更新

    本篇参考: https://developer.salesforce.com/docs/atlas.en-us.234.0.apexref.meta/apexref/apex_methods_syst ...

  4. Java数组3种创建方式

    public static void main(String[] args){ /** * 1. 固定大小的空数组, 动态创建 */ String[] strArr1 = new String[3]; ...

  5. mysql导出csv格式命令

    mysql -h 127.0.0.1 -u user -p123456 -Bse "select name,age from user where age > 10;" | ...

  6. CentOS下搭建自动化测试基础框架:Jenkins+Maven+TestNG+ReportNG

    1. 安装JDK 1.1 卸载系统默认已安装的open-jdk rpm -qa|grep java 查出来openjdk相关的应用,把查出来的所有都要通过下面的命令给卸载掉 rpm -e --node ...

  7. 隐藏键盘的N种方法

    ---Created by luo.h 显示键盘 [textField becomeFirstResponder]; 隐藏键盘 @interface ViewController ()<UITe ...

  8. @play.data.binding.NoBinding

    新的@play.data.binding.NoBinding注解允许我们定义一些"不应该被绑定"的字段,以防出现安全问题.例如: public class User extends ...

  9. MySQL事务以及存储引擎

    MySQL事务以及存储引擎 目录 MySQL事务以及存储引擎 一.事务 1. 事务的概念 2. 事务的ACID特点 (1)原子性 (2)一致性 (3)隔离性 ①事务之间的相互影响 ②MySQL事务支持 ...

  10. shell——mkfifo管道

    转自:http://blog.sina.com.cn/s/blog_605f5b4f0101azuc.html 创建命名管道的方法为:mkfifo pipe_name. 这样就能创建一个命名的管道pi ...