需求背景
设计团队有了新版规范,要求每个后台设置项都拥有单独的设置状态,就像下面这样:
每一项都可以单独点击设置,且可以单独保存提交。
而在设计上,我们当然不希望把所有的设置项写在一个组件内,这会使得一整个组件变得很大,迭代与维护起来非常不方便。
由于后端同学喜欢把数据的获取写在一个接口内返回,就像上图的所有设置项的值,都是通过一个detail接口返回的,其返回值大致如下:
这就遇到了一个问题,我应该把接口请求放在哪里呢?
如果写在各个设置项的组件下:
那第一次进入这个页面的时候,就会有非常多的相同接口请求发出。
而如果放在外层容器中,则一来使得组件互相耦合。
二来这虽然能较好的解决第一次进入时的大量相同接口请求问题,却又会使得单一组件(如客服管理员这个设置项组件)提交设置后,出现发出接口请求获取最新设置时,触发所有设置项的组件数据更新,其流程大致是这样:
外层容器当然可以做到,谁触发通知谁,这种能力,只是这样会比较繁琐。
有没有一个比较简单、优雅的方式可以解决这一问题呢?
解决方案
一个特殊的节流函数可以!其实现大致如下:
function fetchThrottle(fn, limitTime = 1000) {
const resultFn = function(...args) {
if (!resultFn.catchPromise) {
resultFn.catchPromise = fn(...args);
setTimeout(() => {
resultFn.catchPromise = null;
}, limitTime);
}
return resultFn.catchPromise;
}
return resultFn;
}
这个特殊,特殊在哪里?
传统的节流函数会忽略掉某一时间段内的调用,使得只在开始时,或只在结束时的调用被触发。
而我们改进的节流函数的特殊点则是,在一个时间段内,只在第一次的时候才发起接口请求,其后续的调用,都会将第一次调用缓存下来的、请求接口的promise返回。
这就实现了,同一时间段内、多次调用下,只发起一次接口请求,且每个调用方都可以收到一份接口请求的返回值。
其流程大致如下:
使用方法
1、引用工具类,并处理接口请求方法
const sdUtil = require("app-standalone/core/utils");
loadConfigData: sdUtil.fetchThrottle(function (params = {}) {
return callCenterUtil.FHHApi({
url: "/EM1HESERVICE2/eservice/callcenter/setting/queryTenantAdvancedSetting",
params,
});
})
2、各组件自行调用处理后的请求方法
// 设置项组件A
loadConfigDataFn()
.finally((res) => {
this.isLoading = false;
})
.then((res) => {
const data = res.Value.data;
this.isAudioTransferOpen = !!data.voiceToTextStatus;
this.isAutoTransferOpen = !!data.autoTurnTextStatus;
});
// 设置项组件B
loadConfigDataFn()
.finally((res) => {
this.isLoading = false;
})
.then((res) => {
const data = res.Value.data;
this.isAiSummaryOpen = !!data.aiSummaryStatus;
});
实际效果
1、第一次进入,虽各组件自行调用,但接口请求只发起一次。
2、当独立的设置项提交数据更新后、重新请求时,只有对应组件收到通知,进入加载状态,并更新数据值。
写在最后
节流请求,为什么不是防抖呢?
我们可以想象一个场景,我们处理后的方法在持续很长的时间内一直被调用。
这可能源于一个手速、反应力、网速都非常快的超级用户。当然更可能是某处代码有问题,导致错误的频繁调用了,但我们就先假设这是一个正常的需要。
在这样的场景下,如果使用防抖的方法,就会使得下一次的接口请求一次又一次的被推迟调用,这个超级用户就永远无法获得最新的数据了,除非他愿意停下来。
所以节流会是一个稍微好些的选择,起码保证一定时间内都能拿到新数据。