一些简单的封装,主要总结一下 interceptors 的使用。
这一步主要是添加 response 的 interceptors,这里只是处理了当状态为 200 时,将直接返回 data.data
,这样在其他地方获取数据的时候就不需要进行解构。
在没有用 interceptors 之前,解构确实挺烦的。
base url 这种我就直接放在这里了,不过正常情况下一般都是配置到 dotenv 里面去的,然后根据环境选择对应的 base url 之类的,这里偷懒一下
代码如下:
import axios from 'axios'
const instance = axios.create({
baseURL: 'https://dummyjson.com/',
timeout: 1000,
})
instance.interceptors.request.use(req => {
return req
}, err => { })
instance.interceptors.response.use(res => {
const { data, status } = res;
if (status === 200)
return data;
}, err => { })
export default instance;
这个是 React 部分的代码:
const fetchProduct = async () => {
const data = await axios.get('products/1')
console.log(data);
}
useEffect(() => {
fetchProduct()
}, [])
浏览器渲染:
CRUD 部分主要还是对 API 调用进行封装,这一块可以根据使用的 UI 库不同进行微调,主要的作用是:
在 API 调用之前使用遮罩显示 loading 状态
在 API 调用结束后关闭遮罩
loading 状态这一部分可以结合 Redux 或是 RxJS(我们项目目前正在使用的就是 RxJS),亦或者可以直接通过 UI 库完成
返回一个 Promise 让其他地方继续调用
对于错误的处理
…
这一部分可以做的很多,根据业务需求调整即可。
对于调用方面其实差距并不大,只不过是讲一些可以 centralize 的处理集中到了一个函数去处理,省的到处复制黏贴。
代码如下:
import axios from 'axios'
const instance = axios.create({
baseURL: 'https://dummyjson.com/',
timeout: 1000,
})
instance.interceptors.request.use(req => {
return req
}, err => { })
instance.interceptors.response.use(res => {
const { data, status } = res;
if (status === 200)
return data;
}, err => { })
const request = (url, params, options, method) => {
// maybe add loading state here
return new Promise((resolve, reject) => {
let data;
if (method === 'get') {
data = { params };
} else if (method === 'post') {
data = { data: params }
}
instance({
method,
url,
data: {
firstName: 'Fred',
lastName: 'Flintstone'
}
}).then(res => {
resolve(res);
}).catch(e => {
// sent some notification
console.log(e);
}).finally(() => {
// may be remove loading state
})
})
}
export const get = (url, params, options) => {
return request(url, params, options, 'get')
}
export default instance;
React 部分其实没有什么很大的变动:
const fetchProduct = async () => {
// const data = await axios.get('products/1')
// console.log(data);
const data = await get('products/1');
console.log(data);
}
从结果上来说是看不出来有什么区别的:
三次封装主要就是使用一下 interceptors 了。
在这一部分会添加一个 POST 的调用,并且在 interceptors 中判断调用的方法,如果是 POST 或者 PUT 就添加 header。
在 interceptors 中修改 header、token 是一个非常常见的业务逻辑。另外一个处理方法就是在登录之后重新建立一个新的 instance,这两种处理方法都是比较常见的业务情景。
我这里将原本的数组转化成了对象,并且传了两个 success 和 fail 参数,主要是因为我们目前的项目也没有很好的用到 RxJS 的特性(除了 subscribe 之外其他的也没有怎么用……),也没用 Redux,所以很多 callback 的处理就是传俩函数,在 then 和 catch 中进行处理。
如果是使用其他的库,设计好可复用性这种东西,success 和 fail 的存在并不是必须的。
代码如下:
import axios from 'axios'
const instance = axios.create({
baseURL: 'https://dummyjson.com/',
timeout: 1000,
})
const METHOD_TYPE = {
POST: 'post',
GET: 'GET',
PUT: 'PUT'
}
instance.interceptors.request.use(req => {
const { method } = req;
if (method === METHOD_TYPE.POST || method === METHOD_TYPE.PUT)
req.headers = {
...req.headers,
'Content-Type': 'application/json'
};
return req
}, err => { })
instance.interceptors.response.use(res => {
const { data, status } = res;
if (status === 200)
return data;
}, err => { })
const request = (url, params, options, method, success, fail) => {
// maybe add loading here
return new Promise((resolve, reject) => {
let data;
if (method === METHOD_TYPE.GET) {
data = { params };
} else if (method === METHOD_TYPE.POST) {
data = { data: params }
}
instance({
method,
url,
data: {
firstName: 'Fred',
lastName: 'Flintstone'
}
}).then(res => {
success();
resolve(res);
}).catch(e => {
fail()
// sent some notification
console.log(e);
}).finally(() => {
// may be remove loading state
})
})
}
export const get = ({ url, params, options, success, fail }) => {
return request(url, params, options, METHOD_TYPE.GET, success, fail)
}
export const post = ({ url, params, options, success, fail }) => {
return request(url, params, options, METHOD_TYPE.POST, success, fail)
}
export default instance;
React 调用:
const fetchProduct = async () => {
// const data = await axios.get('products/1')
// console.log(data);
const data = await get({
url: 'products/1',
success: () => {
console.log('get success');
// set data etc
},
fail: () => {
console.log('get fail');
}
});
console.log(data);
const postData = await post({
url: 'products/add', params: {
title: 'BMW Pencil',
},
success: () => {
// set data etc
console.log('post success');
},
fail: () => {
console.log('post fail');
}
})
console.log(postData);
}
这部分截图忘了,基本上就是 GET 和 POST 的两个输出。
这一部分就是最近新折腾的取消调用,axios 的官方文档其实给的挺明确的,不过我想太多了……
最后还是试着跑了一下才发现是真的可以成功:
const controller = new AbortController();
instance.interceptors.request.use(req => {
const { method } = req;
if (method === METHOD_TYPE.POST || method === METHOD_TYPE.PUT)
req.headers = {
...req.headers,
'Content-Type': 'application/json'
};
if (method === METHOD_TYPE.POST) {
return {
...req,
signal: controller.signal
}
}
return req
}, err => {})
controller.abort();
这部分的代码中,如果遇到了一些需要放弃调用的情况——如 token 已经过期等,就可以直接在 interceptors 中返回一个 AbortController 的 signal,controller.abort();
会监听这个 signal,存在的情况下就会取消 API 调用。
cancelToken 已经过期了,官方并不推荐使用。
效果如下:
可以看到 React 组件中的 log 是输出了,但是 React 中并没有拿到任何的返回值。
network 中也可以看到没有 POST 的调用,这就可以说明 axios 在请求发送出去之前就已经将其取消了,而不是在发送出去之后关闭连接通道假装没有收到。
基本上来说比较常见的业务封装就是这些了,比较经常遇到的 cases 也差不多就这些,当然,有比较有意思的业务需求可以提出来一起讨论一下,如果解决方案还挺干净的我也可以存下来当作 boilerplate。