前端⼯程化:使⽤shelljs⽣成yapi接⼝⽂件
之前的⽂章介绍了使⽤ yapi-to-typescript (下⽂简称 ytt)⽣成接⼝类型定义⽂件,⽅便我们直接使⽤接⼝的请求和响应类型,能减少很多写接⼝类型的时间。
既然能⽣成接⼝类型定义⽂件,那也就可以⽣成接⼝请求代码⽂件咯,可是⽹上了下没到有相关的⼯具库,想必是每个公司的项⽬接⼝请求代码风格差异较⼤,要开发⼀个可以灵活配置⽣成对应代码的库也略显⿇烦,⽐如这篇⽂章就是教⼤家如何写出⽣成请求代码的⽂件,⽽我也没有写⼀个这样的库。
前置条件
⾸先需要了解两个⼯具库,⼀个是 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() {
// 先删除⽂件,每次重新⽣成
filesName.forEach((fileName) => {
<('-f', getFilePath(fileName));
});
/
/ 遍历接⼝信息⽣成接⼝代码
interfaceInfos.forEach((item) => {
generatorRequest(item);
});
},
}
⽣成接⼝代码
⽣成接⼝代码有⼏点要注意:
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 (de === 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 为 ${}
const convertReqPath = (path: string): string => {
place(/:\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 ⽣成接⼝类型⽂件的同时,也⽣成了这样的请求⽂件:

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。