场景
项目开发中通常会涉及从 服务端下载文件 的需求,发起 ajax请求 后,服务器响应数据通常会有两种情况,第一种是返回 json数据 (一般用于抛出提示),第二种是返回 文件流数据 。
代码设计
为此,我封装了一个支持GET和POST请求的具有提示和下载功能的组件。代码如下:
import { Button, message } from 'antd';
import React from 'react';
type Props = {
/**
* @description 请求路径
*/
url: string;
/**
* @description 请求参数
*/
params: Record<string, any>;
/**
* @description 请求方式 , 默认 'GET'
*/
method?: 'GET' | 'POST';
/**
* @description 下载文件名称 , 默认 'file'
*/
fileName?: string;
[props: string]: any;
};
const AjaxDownloadButton: React.FC<Props> = (props) => {
const { url = '', params = {}, method = 'GET', fileName = 'file', children, ...rest } = props;
const handleClick = () => {
try {
// GET请求的params打平为字符串
const paramsStr = Object.keys(params)
.map((key) => `${key}=${params[key]}`)
.join('&');
const ReqGETUrl = `${url}?${paramsStr}`;
// 根据请求类型配置fetch请求
const promise =
method === 'GET'
? fetch(ReqGETUrl)
: fetch(url, {
method,
body: JSON.stringify(params),
});
// 发起请求,并处理响应数据
promise
.then((response) => response.blob())
.then((data) => {
if (data.type === 'application/json') {
// json数据处理
const reader = new FileReader();
reader.readAsText(data, 'utf-8');
reader.onload = (e) => {
const result = e.target?.result;
if (typeof result === 'string') {
const infoMsg = JSON.parse(result).retMsg;
// 抛出响应数据中的提示信息
message.info(infoMsg);
}
};
} else {
//其余类型都当成文件下载
const a = document.createElement('a');
a.href = window.URL.createObjectURL(data);
a.download = fileName;
a.click();
}
})
.catch(() => {
message.error('请求错误!');
});
} catch (e) {
message.error('请求错误!');
}
};
return (
<Button {...rest} onClick={handleClick}>
{children}
</Button>
);
};
export default AjaxDownloadButton;
组件效果
1.服务端响应数据为文件流数据
先mock出文件流数据
import fetchMock from 'fetch-mock';
fetchMock.mock('example/success/download?success=success', () => '下载成功!');
效果如下:
2.服务端响应数据为json数据
先mock出json数据
import fetchMock from 'fetch-mock';
fetchMock.mock('example/info/download?info=info', () => ({
data: [],
retCode: 'T100',
retMsg: '下载失败,请联系相关人员!',
}));
效果如下:
3.请求响应异常
先mock出异常数据
import fetchMock from 'fetch-mock';
fetchMock.mock('example/error/download?error=error', () => {
throw new Error('下载错误!');
});
FAQ
1、为什么用fetch发起请求?
流行框架的请求插件底层一般是基于fetch封装的,但是有时候框架的请求插件配置会约束我们的设计。比如,我开发的项目其实是基于umi框架的,正常情况下使用 @umijs/plugin-request 的 useRequest去发起请求是很方便的,还会内置提示和错误抛出功能,但是像下载文件这种请求比较特殊,会有两种返回数据格式,因为也需要为了这个需求,去app.ts文件对@umijs/plugin-request进行请求成功情况的配置,不然文件流就会被当成错误提示抛出。所以为了组件更加通用性,选择了用原生的fetch发起请求。