0%

axios

开始了开始了,一点点搞起来。

关于我在写 axios 时候遇到的问题

安装

首先我使用的是 yarn create react-app –template typescript 创建基础的 react 项目脚手架

然后使用的是 yarn eslint –init 去控制格式,其实这个我还没有搞好,好多规则都不知道怎么回事,保存跟格式化的格式都不一样

之后安装 axios

跨域

在尝试 axios 的时候遇到的第一个问题就是跨域

首先在 package.json 中,我们可以使用字符串去指定需要代理的地址,如下:

1
2
3
4
5
6
7
{
"name": "axios-react-test",
"version": "0.1.0",
"private": true,
// ...其他配置
"proxy": "http://hrms.test.bbkedu.com/"
}

当然,这样也够用,但是项目大起来之后就会需要根据不同的路径匹配不同的请求地址,所以还需要配置其他的东西。

既然 package.json 可以配置 proxy,那我们可以不可以使用对象来配置呢?

果断试试看:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
{
"name": "axios-react-test",
"version": "0.1.0",
"private": true,
// ...其他配置
"proxy": {
"/api/**": {
"target": "http://hrms.test.bbkedu.com/",
"changeOrigin": true
},
"/note/**": {
"target": "http://bpm2.test.bbkedu.com/",
"changeOrigin": true
}
}
}

When specified, "proxy" in package.json must be a string.

Instead, the type of "proxy" was "object".

Either remove "proxy" from package.json, or make it a string.

重新启动项目就会看到上面这几个大字,大致意思就是不允许使用对象,只能是字符串

_据说在 create-react-app 2.0.0 的版本之前,可是使用对象来配置代理_,反正我是没有试过。

那我们该怎么配置呢?这是个好问题,而官方文档已经给出了解决方法

  1. 安装 http-proxy-middleware 中间件,umi 的代理也是用的这个,只不过包裹在了 umirc 中

  2. 在 src/ 下创建一个 setupProxy.js 必须是 .js 其他的不认的哈 .ts 也不认。

  3. 编写 setupProxy.js

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    // 这里也不能够使用 import/export 模式,只能使用 commonJs。就很🐕
    const { createProxyMiddleware } = require("http-proxy-middleware");

    module.exports = function (app) {
    app.use(
    "/api",
    createProxyMiddleware({
    target: "http://hrms.test.bbkedu.com",
    changeOrigin: true,
    })
    );
    };

第二点中提到固定名字文件的,是 react 拆包之后某个文件中引入了这个文件,固定了名字。😭 我还没有拆过包,或者已经忘记了。

将 create-react-app 解包后,可以在 config 文件夹下找到配置

在 config/path.js 中存在 proxySetup: resolveApp(‘src/setupProxy.js’),

而 proxySetup 是只在 webpackDevServer.config.js 文件中使用,也就是说只在开发时使用

上面这三行来自Spirit Ling 博客

基本实现封装

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
import axios, { AxiosRequestConfig } from "axios";
import { TOKEN } from "../config";

export interface Data {
classgroup: string;
company: string;
department: string;
id: string;
rawDate: string;
rootdepartment: string;
trueName: string;
userNo: string;
}

interface NoRecordPageParams {
userNo: string;
size: number;
index: number;
}

const CancelToken = axios.CancelToken;
const source = CancelToken.source();

// 添加请求拦截器
// 拦截器可以做什么操作???
axios.interceptors.request.use(
function (config) {
// 在发送请求之前做些什么
// 添加/修改 TOKEN 等 headers 信息
console.log(config);
config.headers = {
...config.headers,
Authorization: `Bearer ${TOKEN}`,
};
config.timeout = 10000;
return config;
},
function (error) {
// 对请求错误做些什么
// 能够拦截错误,程序不再往下执行?
// 能不能做什么操作让程序往下执行呢,比如按钮loading状态恢复?
// 还是说只能用trycatch
// console.log('请求错误', error)
return Promise.reject(error);
}
);

axios.interceptors.response.use(
(value) => value,
function (error) {
console.log("返回错误", error);
return Promise.reject(error);
}
);

async function request<P, R>(
url: string,
config: AxiosRequestConfig<P>
): Promise<R> {
try {
const res = await axios({
...config,
url,
});
// 返回内容为 blob 格式的情况
if (res.headers["content-disposition"]) {
const response = {
title: decodeURI(
res.headers["content-disposition"].split(";")[1].split("filename=")[1]
),
blob: res.data,
};
return response as unknown as R;
}
// 返回正常数据,且错误的情况
if (res && res.data.code !== "0000001") {
console.error("程序错误", res.data.desc);
return Promise.reject("Reject123");
}
// 返回正常数据
return res.data;
} catch (error) {
// console.log(error)
if (axios.isCancel(error)) {
console.error("取消请求", error);
} else {
// 处理错误
console.error("other error");
}
return Promise.reject("Reject");
}
}

// 查询全部
// 查询组织
export const getOrgs = async () => {
return await axios.get("/api/org");
};

// 按条件查找
// 获取无打卡记录
export const getNoRecordPage = async (params: NoRecordPageParams) => {
// console.log(source.token)
return request<NoRecordPageParams, Data[]>(
"/api/attendance/getNoRecordPage",
{
method: "GET",
params,
cancelToken: source.token,
}
);
};

// 获取无打卡记录
export const exportNoRecord = async (
params: Pick<NoRecordPageParams, "userNo">
) => {
source.cancel("cancel fetch");
return request<
Pick<NoRecordPageParams, "userNo">,
{
title: string;
blob: Blob;
}
>("/api/attendance/export/NoRecord", {
method: "GET",
params,
responseType: "blob",
});
};

其中需要拎出来单独讲的一部分内容

  1. 下载

    1
    2
    3
    4
    5
    6
    7
    8
    9
    if (res.headers["content-disposition"]) {
    const response = {
    title: decodeURI(
    res.headers["content-disposition"].split(";")[1].split("filename=")[1]
    ),
    blob: res.data,
    };
    return response as unknown as R;
    }

    按照常理来说,下载文件时后台返回给前端 headers 中需要包含content-disposition属性,其他请求则没有,它是一个字符串,包含了 filename 属性,可以给 blob 做文件名。

    我们在请求下载的时候需要添加额外的请求参数 responseType: 'blob',告知服务器返回的是数据流。

  2. 取消请求

    cancelToken: source.token 确实能够取消请求,但是后续的请求都会报错(直接取消)

  3. 请求中间件(请求拦截)

    当大多数的请求都用到了某个 config 配置(例如 token、timeout 等),就可以考虑将这个配置放进 axios.interceptors.request.use 中,具体看上面代码。返回结果也是,当然是写在 response 中。