问题引入
开发过程中会遇到类似下拉框后端搜索的功能,此时键入key,onChange会频繁触发,可能导致
①频发无用请求(仅当用户停止输入,才有可能是最终想要搜索的key),
②导致请求时序错乱(即一前一后两个请求,后请求的已返回,而前请求的未返回)
防抖与节流
防抖
从开始执行方法时计时,每再执行一次方法,重新计时。如:设定防抖时间200ms,则在搜索框应用场景内,仅当用户输入停顿达到两秒,才会发送请求,一定程度上避免过度请求带来的问题
原生实现
// 防抖
function debounce(fn, wait) {
var timeout = null; // 利用闭包存住timeout
return function() {
if(timeout !== null) {
clearTimeout(timeout);
}
timeout = setTimeout(fn, wait);
}
}
React hooks版
import { useEffect, useRef } from 'react'
const useDebounce = (fn, ms = 30, deps = []) => {
let timeout = useRef()
useEffect(() => {
if (timeout.current){
clearTimeout(timeout.current);
}
timeout.current = setTimeout(() => {
fn()
}, ms)
}, deps)
const cancel = () => {
clearTimeout(timeout.current)
timeout = null
}
return [cancel]
}
export default useDebounce;
// 用法
import { useDebounce } from 'hooks'
const Home = (props) => {
const [a, setA] = useState(0)
const [b, setB] = useState(0)
const [cancel] = useDebounce(() => {
setB(a)
}, 2000, [a])
const changeIpt = (e) => {
setA(e.target.value)
}
return <div>
<input type="text" onChange={changeIpt} />
{ b } { a }
</div>
}
节流
节流思路为,若设置的时间为200ms,则200ms间隔时间未到,不会再次执行函数。
原生实现
// 节流
function throttle(func, ms) {
let previous = 0;
return function() {
let now = Date.now();
let context = this;
let args = arguments;
if (now - previous > ms) {
func.apply(context, args);
previous = now;
}
}
}
偷懒版
lodash
import { debounce, throttle } from 'lodash';
debounce(() => {
//DO SOMETHING
}, 200);
throttle (() => {
//DO SOMETHING
}, 200);
解决请求时序问题
防抖与节流并不能100%解决异步请求过于频繁带来的请求response错乱的问题,当两个相同请求,响应时间差异过大第一个请求慢于第二个请求返回,此时若不作处理,正常流程走完以后会发现第一个请求的结果覆盖了第二个请求,此时需要对结果做处理,即抛弃第一个请求的结果
实现思路
这里以dva.js为例
1.我们需要两个容器存储 最后一次请求的时间(lastResponseTime
) 和 最新请求的结果(responseCache
)
2.当一个请求,如 *getXXX()开始时,获取当前时间currentTime
3.当异步请求执行完毕,将currentTime
与lastResponseTime
作对比,当currentTime
>lastResponseTime
,则说明该请求的结果为最新的,将其存入responseCache
并更新lastResponseTime
= currentTime
并使用当前请求的结果;否侧弃用当前请求的结果,沿用responseCache
。
state: {
lastResponseTime: new Date(),
responseCache: undefined,
},
*getXXX({ payload, callback }, { call, put, select }){
const currentTime = new Date().getTime();
const response= yield call(getXXX, payload);
const { lastResponseTime} = yield select(_ => _.MODULE_NAMESPACE_XXX);
if(currentTime > lastResponseTime){
yield put({
type: 'updateState',
state: {
responseCache: response,
lastResponseTime: currentTime ,
},
});
callback(response);
} else {
const { responseCache } = yield select(_ => _.MODULE_NAMESPACE_XXX);
callback(responseCache);
}
}
另一种思路
上面的实现思路是控制请求结果,我们还可以从请求开始出发。
即,当上一个请求未返回,则不发送下一个请求,直到上一个请求结束。
该种情况不适用于搜索,因为搜索更注重结果,所以需要取最新一次请求的结果。此种情况适用于状态轮询的应用场景,如小明正在吃早餐,当我们需要知道小明是否已经吃完了,隔两分钟就去询问一下,如果其中一次小明还未回复,则不再问他了
可以借助dva.js的loading状态来判断是否正在pending
setInterval(()=>{
if(!getXXXLoading){
dispatch({
type: MODULE_NAMESPACE_XXX/getXXX,
payload:{},
callback: ()=>{}
})
}
}, 200)
注意
dva.js的loading存在问题:依次发送两个相同请求,当其中一个返回,loading状态就为false。
1.定义不同的effects
2.手动维护loading状态
持续关注该问题是否已解决