背景
项目中经常会出现一种业务场景:一个‘输入框’、一个‘搜索按钮’,点击按钮,按输入字符串进行搜索。相信很多人都可能存在一种情况——重复点击按钮进行搜索,并且使用方式可能会是以下代码的形式。
async search() {
this.list = await this.fetchData(queryStr);
}
在多次点击按钮时,会存在一个性能问题:返回数据没变,但是又重新渲染页面了。
方案
当然,对于这种比较常见的性能问题,有多种方案去处理。这里,我主要给出的是Axios层面封装防抖函数,来处理这一类问题。采用这种方案,是因为Axios的封装可以针对项目的所有请求,而不单单是此场景下的单个问题。
这里额外说一点就是,浏览器cancel请求的原理,其实是浏览器发出了请求,服务器也处理返回了数据,但是浏览器监听到cancel消息后,放弃了对该接口的监听,所以也不会处理数据。
另外,主动cancel请求会被try/catch所捕获,如果你项目封装catch的时候有toast错误的话,要注意这一点。
代码其实也比较简单,不过多赘述,直接附上完整代码。
完整代码
// cancelConfig.ts
import qs from 'qs';
import { AxiosRequestConfig, CancelToken, CancelTokenStatic, CancelTokenSource } from 'axios';
export interface AxiosCancelConfigParams extends AxiosRequestConfig {
source?: CancelTokenSource;
}
export default class AxiosCancelConfig {
cancelKeyMap: Map<string, CancelTokenSource>;
constructor() {
this.cancelKeyMap = new Map();
}
getKey(config: AxiosCancelConfigParams): string {
const { method = 'GET', url, data = {}, params = {} } = config;
return `${method}${url}${qs.stringify(data)}${qs.stringify(params)}`;
}
add(config: AxiosCancelConfigParams, source: CancelTokenSource) {
const key = this.getKey(config);
if (config.source) {
if (!this.cancelKeyMap.has(key)) {
this.cancelKeyMap.set(key, config.source);
}
} else {
config.cancelToken = source.token;
if (!this.cancelKeyMap.has(key)) {
this.cancelKeyMap.set(key, source);
}
}
}
cancel(config: AxiosCancelConfigParams) {
const key = this.getKey(config);
if (this.cancelKeyMap.has(key)) {
const source = this.cancelKeyMap.get(key);
source?.cancel();
this.remove(config);
}
}
remove(config: AxiosCancelConfigParams) {
const key = this.getKey(config);
if (this.cancelKeyMap.has(key)) {
this.cancelKeyMap.delete(key);
}
}
}
// config.js
// 请求拦截器:如果存在source或者cancelToken,则认为该接口自行处理cancel行为 ,否则默认进行防抖;
axiosCancelConfigInstance = new AxiosCancelConfig;
if (!config.source && !config.cancelToken) {
const source = axios.CancelToken.source();
axiosCancelConfigInstance.cancel(config);
axiosCancelConfigInstance.add(config, source);
}
// 响应拦截器:
try {
const axiosRes = await service(options);
await axiosCancelConfigInstance.remove(axiosRes.config);
const res = axiosRes.data;
.....
} catch (error) {
//...
const __CANCEL__ = (error as any).__proto__.__CANCEL__ || false;
if (__CANCEL__) {
return Promise.reject();
}
errorToast((error as any).message);
return Promise.reject();
}