React Hook:给 useEffect 增加节流和防抖能力
前言
本文参考 react-use 等 React Hook 库的源码,解读拥有防抖和节流能力的 useEffect钩子的实现(当然useLayoutEffect钩子实现防抖节流原理也是一样的)。
防抖(Debounce)是指在事件被触发某个给定delay时间后再执行回调callback函数,如果事件在delay到期之前被再次触发,则重新计时。在搜索框等组件可以上面经常看到这类设计。
节流(Throttle)是指在某个给定delay时间内,只能有一次触发事件的回调函数callback执行,如果在delay时间内某事件被触发多次,回调函数callback只触发一次。节流可以使用在滚动事件监听上,降低回调函数触发频率减少可能的卡顿。
防抖
react-use 的useDebounce依赖于useTimeoutFn生成的带防抖效果的useEffect:
ts- import { DependencyList, useEffect } from 'react';
- import useTimeoutFn from './useTimeoutFn';
- export type UseDebounceReturn = [() => boolean | null, () => void];
- export default function useDebounce(
- fn: Function,
- ms: number = 0,
- deps: DependencyList = []
- ): UseDebounceReturn {
- const [isReady, cancel, reset] = useTimeoutFn(fn, ms);
- useEffect(reset, deps);
- return [isReady, cancel];
- }
useTimeoutFn生成了ms时间后执行的防抖回调函数reset,在useDebounce中useTimeoutFn返回的reset会因依赖deps变化反复触发,当在ms到期前触发时,则会重置定时器重新计时。
ts- import { useCallback, useEffect, useRef } from 'react';
- export type UseTimeoutFnReturn = [() => boolean | null, () => void, () => void];
- export default function useTimeoutFn(fn: Function, ms: number = 0): UseTimeoutFnReturn {
- const ready = useRef<boolean | null>(false);
- const timeout = useRef<ReturnType<typeof setTimeout>>();
- const callback = useRef(fn);
- const isReady = useCallback(() => ready.current, []);
- const set = useCallback(() => {
- ready.current = false;
- timeout.current && clearTimeout(timeout.current);
- timeout.current = setTimeout(() => {
- ready.current = true;
- callback.current();
- }, ms);
- }, [ms]);
- const clear = useCallback(() => {
- ready.current = null;
- timeout.current && clearTimeout(timeout.current);
- }, []);
- // update ref when function changes
- useEffect(() => {
- callback.current = fn;
- }, [fn]);
- // set on mount, clear on unmount
- useEffect(() => {
- set();
- return clear;
- }, [ms]);
- return [isReady, clear, set];
- }
这样子就可以实现useEffect的防抖了。类似的,我们来重复造一个轮子,再加上限制最长防抖时间的优化:
// 下面的updateEffect看这篇文章噢
ts- type debounceEffectOptions = {
- delay?: number,
- maxDelay?: number,
- }
- export const debounceEffect = (
- effectHook: (effect: React.EffectCallback, deps: React.DependencyList) => void
- ) =>
- (
- effect: React.EffectCallback, deps: React.DependencyList, options: debounceEffectOptions = {}
- ) => {
- const timeout = useRef<ReturnType<typeof setTimeout> | null>(null)
- const callback = useRef(effect)
- const {
- delay = 200,
- maxDelay = Infinity
- } = options
- effectHook(() => {
- callback.current = effect
- }, [effect])
- const run = useCallback(() => {
- timeout.current && clearTimeout(timeout.current)
- timeout.current = setTimeout(() => {
- timeStart.current = Date.now()
- callback.current()
- clearTimeout(timeout.current as number)
- timeout.current = null
- }, delay)
- }, [delay])
- const stop = useCallback(() => {
- timeout.current && clearTimeout(timeout.current)
- timeout.current = null
- }, [])
- const delayTimer = useRef<ReturnType<typeof setInterval> | null>(null)
- const timeStart = useRef(0)
- useEffect(() => {
- delayTimer.current && clearInterval(delayTimer.current)
- delayTimer.current = setInterval(() => {
- if ((Date.now() - timeStart.current) / 1000 >= maxDelay) {
- stop()
- callback.current()
- }
- }, 20);
- }, [maxDelay, delay, run, stop])
- effectHook(() => {
- run()
- }, [delay])
- effectHook(() => {
- run()
- }, deps)
- return [run, stop]
- }
- export const useDebounceEffect = debounceEffect(useUpdateEffect)
- export const useDebounceLayoutEffect = debounceEffect(useUpdateLayoutEffect)
节流
react use 中,节流有两个钩子,分别是useThrottleFn和useThrottle。但是主要用于节流组件状态的改变,并没有针对回调触发的钩子,我们下面就写一个。节流的逻辑主要是让某个时间内函数只执行一次:
ts- type throttleEffectOptions = {
- delay?: number,
- }
- export const throttleEffect = (
- effectHook: (effect: React.EffectCallback, deps: React.DependencyList) => void
- ) =>
- (
- effect: React.EffectCallback, deps: React.DependencyList, options: throttleEffectOptions = {}
- ) => {
- const timeout = useRef<ReturnType<typeof setTimeout> | null>(null)
- const trigger = useRef(false)
- const callback = useRef(effect)
- const {
- delay = 200,
- } = options
- effectHook(() => {
- callback.current = effect
- }, [effect])
- const run = useCallback(() => {
- if (!timeout.current) {
- timeout.current = setTimeout(() => {
- callback.current()
- clearTimeout(timeout.current as number)
- timeout.current = null
- if (trigger.current) {
- run()
- }
- }, delay)
- } else {
- trigger.current = true
- }
- }, [delay])
- const stop = useCallback(() => {
- timeout.current && clearTimeout(timeout.current)
- timeout.current = null
- }, [])
- effectHook(() => {
- run()
- }, deps)
- return [run, stop]
- }
- export const useThrottleEffect = throttleEffect(useUpdateEffect)
- export const useThrottleLayoutEffect = throttleEffect(useUpdateLayoutEffect)
0 条评论未登录用户
Ctrl or + Enter 评论
