防抖和节流及对应的React Hooks封装
2021-02-22 00:08
1601 查看
Debounce
debounce 原意
消除抖动,对于事件触发频繁的场景,只有最后由程序控制的事件是有效的。
防抖函数,我们需要做的是在一件事触发的时候设置一个定时器使事件延迟发生,在定时器期间事件再次触发的话则清除重置定时器,直到定时器到时仍不被清除,事件才真正发生。
const debounce = (fun, delay) => { let timer; return (...params) => { if (timer) { clearTimeout(timer); } timer = setTimeout(() => { fun(...params); }, delay); }; };
如果事件发生使一个变量频繁变化,那么使用
debounce可以降低修改次数。通过传入修改函数,获得一个新的修改函数来使用。
如果是
class组件,新函数可以挂载到组件
this上,但是函数式组件局部变量每次
render都会创建,
debounce失去作用,这时需要通过
useRef来保存成员函数(下文
throttle通过
useRef保存函数),是不够便捷的,就有了将
debounce做成一个
hook的必要。
function useDebounceHook(value, delay) { const [debounceValue, setDebounceValue] = useState(value); useEffect(() => { let timer = setTimeout(() => setDebounceValue(value), delay); return () => clearTimeout(timer); }, [value, delay]); return debounceValue; }
在函数式组件中,可以将目标变量通过
useDebounceHook转化一次,只有在满足
delay的延迟之后,才会触发,在
delay期间的触发都会重置计时。
配合
useEffect,在
debounce value改变之后才会做出一些动作。下面的
text这个
state频繁变化,但是依赖的是
debounceText,所以引发的
useEffect回调函数却是在指定延迟之后才会触发。
const [text,setText]=useState(''); const debounceText = useDebounceHook(text, 2000); useEffect(() => { // ... console.info("change", debounceText); }, [debounceText]); function onChange(evt){ setText(evt.target.value) }
上面一个搜索框,输入完成
1秒(指定延迟)后才触发搜索请求,已经达到了防抖的目的。
Throttle
throttle 原意
节流阀,对于事件频繁触发的场景,采用的另一种降频策略,一个时间段内只能触发一次。
节流函数相对于防抖函数用在事件触发更为频繁的场景上,滑动事件,滚动事件,动画上。
看一下一个常规的节流函数 (ES6):
function throttleES6(fn, duration) { let flag = true; let funtimer; return function () { if (flag) { flag = false; setTimeout(() => { flag = true; }, duration); fn(...arguments); // fn.call(this, ...arguments); // fn.apply(this, arguments); // 运行时这里的 this 为 App组件,函数在 App Component 中运行 } else { clearTimeout(funtimer); funtimer = setTimeout(() => { fn.apply(this, arguments); }, duration); } }; }
(使用
...arguments和 call 方法调用展开参数及apply 传入argument的效果是一样的)
扩展:在
ES6之前,没有箭头函数,需要手动保留闭包函数中的this和参数再传入定时器中的函数调用:所以,常见的
ES5版本的节流函数:function throttleES5(fn, duration) { let flag = true; let funtimer; return function () { let context = this, args = arguments; if (flag) { flag = false; setTimeout(function () { flag = true; }, duration); fn.apply(context, args); // 暂存上一级函数的 this 和 arguments } else { clearTimeout(funtimer); funtimer = setTimeout(function () { fn.apply(context, args); }, duration); } }; }
如何将节流函数也做成一个自定义
Hooks呢?上面的防抖的
Hook其实是对一个变量进行防抖的,从一个 ad8
不间断频繁变化的变量得到一个
按照规则(停止变化delay时间后)才能变化的变量。我们对一个变量的变化进行节流控制,也就是从一个
不间断频繁变化的变量到
指定duration期间只能变化一次(结束后也会变化)的变量。
throttle对应的
Hook实现:
(标志能否调用值变化的函数的
flag变量在常规函数中通过闭包环境来保存,在
Hook中通过
useRef保存)
function useThrottleValue(value, duration) { const [throttleValue, setThrottleValue] = useState(value); let Local = useRef({ flag: true }).current; useEffect(() => { let timer; if (Local.flag) { Local.flag = false; setThrottleValue(value); setTimeout(() => (Local.flag = true), duration); } else { timer = setTimeout(() => setThrottleValue(value), duration); } return () => clearTimeout(timer); }, [value, duration, Local]); return throttleValue; }
对应的在手势滑动中的使用:
export default function App() { const [yvalue, setYValue] = useState(0); const throttleValue = useThrottleValue(yvalue, 1000); useEffect(() => { console.info("change", throttleValue); }, [throttleValue]); function onMoving(event, tag) { const touchY = event.touches[0].pageY; setYValue(touchY); } return ( <div onTouchMove={onMoving} style={{ width: 200, height: 200, backgroundColor: "#a00" }} /> ); }
这样以来,手势的
yvalue值一直变化,但是因为使用的是
throttleValue,引发的
useEffect回调函数已经符合规则被节流,每秒只能执行一次,停止变化一秒后最后执行一次。
对值还是对函数控制
上面的
Hooks封装其实对值进行控制的,第一个防抖的例子中,输入的
text跟随输入的内容不断的更新
state,但是因为
useEffect是依赖的防抖之后的值,这个
useEffect的执行是符合防抖之后的规则的。
可以将这个防抖规则提前吗? 提前到更新
state就是符合防抖规则的,也就是只有指定延迟之后才能将新的
value进行
setState,当然是可行的。但是这里搜索框的例子并不好,对值变化之后发起的请求可以进行节流,但是因为搜索框需要实时呈现输入的内容,就需要实时的
text值。
对手势触摸,滑动进行节流的例子就比较好了,可以通过设置
duration来控制频率,给手势值的
setState降频,每秒只能
setState一次:
export default function App() { const [yvalue, setYValue] = useState(0); const Local = useRef({ newMoving: throttleFun(setYValue, 1000) }).current; useEffect(() => { console.info("change", yvalue); }, [yvalue]); function onMoving(event, tag) { const touchY = event.touches[0].pageY; Local.newMoving(touchY); } return ( <div onTouchMove={onMoving} style={{ width: 200, height: 200, backgroundColor: "#a00" }} /> ); } //常规节流函数 function throttleFun(fn, duration) { let flag = true; let funtimer; return function () { if (flag) { flag = false; setTimeout(() => (flag = true), duration); fn(...arguments); } else { clearTimeout(funtimer); funtimer = setTimeout(() => fn.apply(this, arguments), duration); } }; }
这里就是对函数进行控制了,控制函数
setYValue的频率,将
setYValue函数传入节流函数,得到一个新函数,手势事件中使用新函数,那么
setYValue的调用就符合了节流规则。如果这里依然是对手势值节流的话,其实会有很多的不必要的
setYValue执行,这里对
setYValue函数进行节流控制显然更好。
需要注意的是,得到的新函数需要通过
useRef作为“实例变量”暂存,否则会因为函数组件每次render执行重新创建。
相关文章推荐
- 通过实例学习React中事件节流防抖
- 帮你彻底搞懂防抖和节流(附带在React使用的一个例子)
- 移动端事件(五)—— 函数防抖和函数节流的封装
- 函数防抖和函数节流
- 如何封装HAL API以及对应的JAVA API
- react-native 在android封装原生listView
- ReactNative——fetch封装新姿势
- React 目标 了解组件以及组件的封装
- React中怎么通过id更改对应的数组元素。
- React 进阶:Hooks 该怎么用
- 从零开始 React Native(6) 计时器案例(模块化_组件封装)有图有demo
- 防抖和节流
- reactwebAPP的各个页面之间的跳转以及footer相对应!
- js中连续触发事件的稀释方法(函数节流、函数防抖、标识变量)
- react-native IOS端原生组件封装步骤
- React Js Simditor Textarea 富文本的组件封装
- react-hooks、resRef、Next
- 面试准备Js复习之防抖,节流函数原理
- 函数的节流 & 防抖
- 【react-native-0.31-iOS】封装原生组件并调用(02)