开始了开始了,一点点搞起来。
关于我在写 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 的版本之前,可是使用对象来配置代理_,反正我是没有试过。
那我们该怎么配置呢?这是个好问题,而官方文档已经给出了解决方法
安装 http-proxy-middleware
中间件,umi 的代理也是用的这个,只不过包裹在了 umirc 中
在 src/ 下创建一个 setupProxy.js
必须是 .js 其他的不认的哈 .ts 也不认。
编写 setupProxy.js
1 2 3 4 5 6 7 8 9 10 11 12
| 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) { console.log(config); config.headers = { ...config.headers, Authorization: `Bearer ${TOKEN}`, }; config.timeout = 10000; return config; }, function (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, }); 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) { 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) => { 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 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'
,告知服务器返回的是数据流。
取消请求
cancelToken: source.token
确实能够取消请求,但是后续的请求都会报错(直接取消)
请求中间件(请求拦截)
当大多数的请求都用到了某个 config 配置(例如 token、timeout 等),就可以考虑将这个配置放进 axios.interceptors.request.use
中,具体看上面代码。返回结果也是,当然是写在 response
中。