orval初步使用
简介
orval 是一款类似于 Alibaba Pont 的一款作品,用于对接后端 openApi(swagger)文档,生成前端接口封装+ts类型的插件!
只不过后者已经被弃更了,貌似大厂的KPI项目而已!
orval中文文档
可惜的是官方并没有出中文文档,好在他们的文档开源而且是markdown,我直接fork了一份,利用 AI GPT来了个全文翻译(比谷歌浏览器自带的翻译好)!
并部署到了自己的服务器上,直接访问即可:https://orval.dingshaohua.com
构建和部署心得(可不看)
另外因为是 nextjs,考虑便于被搜索,官方部署的方式是服务端部署,也就是 npm build + npm start
,但这会需要启动一个服务,我打包成静态资源即可!
官方仓库并没有做这些,所以需要我改动一些东西
修改next.config.js如下,再次运行 build 就是客户端打包了,项目会多出 out 目录为产出
module.exports = {
// 添加以下代码
output: 'export',
trailingSlash: true,
images: {
unoptimized: true
},
// 注释以下代码
// rewrites() {
// return [
// {
// source: '/docs{/}?',
// destination: '/docs/overview',
// },
// ];
// },
// ... 其它不变
}
解构数据
比如我有个登录接口,返回数据如下:
{
code:1,
msg:'ok',
data:{
user:{
name:'张三'
}
}
}
不解构
不解构数据的情况下,你得这么用
const res = await api.login();
console.log(res.data.data.user)
解构
结构需要自定义 api实例(客户端),也就是要给orval配置
orval.config.ts
// orval.config.ts
import { defineConfig } from 'orval';
export default defineConfig({
petstore: {
input: 'http://localhost:3003/api/doc', // ① 规范文件
output: {
mode: 'tags', // ② 按 tag 分子文件,好维护
target: './src/lib/api/endpoints', // ③ 生成 *.ts 目录
schemas: './src/lib/api/model', // ④ 生成的类型定义目录
mock: false, // ⑥ 同时生成 MSW mock
clean: true, // 👈 每次生成前清目录
override: {
// 自定义 axios 实例,让 Orval 知道我们的拦截器已经解构了 response.data
mutator: {
path: './src/lib/api/api.base.ts',
name: 'customAxiosInstance',
}
},
},
},
});
解构一层
自定义的api客户端要这么定义
api.base.ts
export const customAxiosInstance = <T>(
config: AxiosRequestConfig,
options?: AxiosRequestConfig,
): Promise<T> => {
const promise = axios({
...config,
...options,
}).then(({ data }) => data);
return promise;
};
仅会把axios自动包裹的结构给移除
const res = await api.login();
console.log(res.data.user)
解构两层
自定义的api客户端要这么定义
api.base.ts
axios.interceptors.response.use(
(response) => {
const res = response.data;
if (res.code === 1) {
toast.error(res.msg);
return Promise.reject(res.msg);
} else {
return res.data;
}
},
(error) => {
return Promise.reject(error);
},
);
// 类型工具:提取 API 响应中 data 字段的类型
type ExtractDataType<T> = T extends { data: infer D } ? D : T;
export const customAxiosInstance = <T>(
config: AxiosRequestConfig,
options?: AxiosRequestConfig,
): Promise<ExtractDataType<T>> => {
// 直接返回 axios 调用的结果
// 响应拦截器已经处理了数据解构,返回的就是最终的业务数据
return axios({
...config,
...options,
}) as Promise<ExtractDataType<T>>;
};
这样,不仅要把axios自动包裹的结构给移除,还要把后端统一包裹的结构(jsonResult)给移除
const res = await api.login();
console.log(res.user)
方案总结:
- 响应拦截器已经解构了一层数据(返回
res.data
) - 但
customAxiosInstance
的泛型T
仍然指向完整的响应结构JsonResultTypeTokenStringMeUser
- 导致 TypeScript 认为返回的还是完整结构,需要访问
.data
属性
解决方案:
- 添加类型工具
ExtractDataType<T>
:自动提取 API 响应中data
字段的类型 - 修改
customAxiosInstance
返回类型:从Promise<T>
改为Promise<ExtractDataType<T>>
- 类型断言:确保返回的类型与实际解构后的数据一致