3个自定义防抖Hooks的实现原理

3,490 阅读2分钟

携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第 1 天,点击查看活动详情

背景

现在有一个需求,是在更新 Input 输入框时,搜索数据。

类似的功能图如下:

image.png

通过 react useEffect,简单的 demo 实现如下:

import { Input } from 'antd'
import { useEffect, useState } from 'react'
import './App.css'

function App() {
  const [val, setVal] = useState('')

  const onSearch = (val) => {
    console.log('搜索', val || '全部')
  }

  // 当 val 发生变化时,请求搜索数据
  useEffect(() => {
    onSearch(val)
  }, [val])

  return (
    <div className='App'>
      <Input value={val} placeholder='请输入' onChange={(e) => setVal(e.target.value)} allowClear />
    </div>
  )
}

这时可以看到,首次进入页面,会发起 2 次查询全部的搜索数据请求,然后每次输入框更新,都会发起搜索数据的请求。

image.png

为了优化性能,我们可以在搜索数据时,加入防抖的逻辑,只有当输入操作停顿指定时间后,才发起搜索数据的请求。

lodash debounce + useCallback

引入 lodash 的 debounce 方法。

lodash_.debounce(func, [wait=0], [options=])创建一个 debounced(防抖动)函数,该函数会从上一次被调用后,延迟 wait 毫秒后调用 func 方法。

将 onSearch 方法用 lodash.debounce + useCallback 封装后,可以实现防抖效果。

const onSearch = useCallback(
    debounce((val) => {
      console.log('搜索', val || '全部')
    }, 500),
    []
  )

image.png

useDebounceFn

将上述 lodash debounce + useCallback 封装为自定义 Hooks useDebounceFn,useDebounceFn 将返回一个有防抖效果的函数。

useDebounceFn(fn1, options) 返回防抖 Hooks。

interface DebounceOptions {
  wait?: number
}

const useDebounceFn = (fn: (...args: any) => any, options: DebounceOptions) => {
  return useCallback(debounce(fn, options.wait), [])
}

const onSearch = useDebounceFn(
    (val) => {
      console.log('搜索', val || '全部')
    },
    {
      wait: 500,
    }
  )

useDebounce

在 useDebounceFn 基础上,实现 useDebounce,返回一个具有防抖效果的 state。

创建一个新 state,setState 用 useDebounceFn 封装,useDebounce(state, options) 返回防抖 state。

function useDebounce<T>(value: T, options: DebounceOptions) {
  const [debounced, setDebounced] = useState(value)

  const update = useDebounceFn((value) => {
    setDebounced(value)
  }, options)

  useEffect(() => {
    update(value)
  }, [value])

  return debounced
}

将 useEffect 的依赖项改成 useDebounce 返回的 state,同样可以实现搜索防抖:

const debounceVal = useDebounce(val, { wait: 500 })
const onSearch = (val: string) => {
console.log('搜索', val || '全部')
}

// 当 debounceVal 发生变化时,请求搜索数据
useEffect(() => {
onSearch(debounceVal)
}, [debounceVal])

useDebounceEffect

在 useDebounceFn 基础上,实现 useDebounceEffect,返回一个具有防抖效果的 useEffect。

创建一个新 state,setState 用 useDebounceFn 封装,依赖更新时防抖更新 state,新 state 更新时执行副作用,这时副作用就防抖执行了。

useDebounceEffect(effect, deps, options) 返回防抖 useEffect。

function useDebounceEffect(effect: EffectCallback, deps: DependencyList, options: DebounceOptions) {
  const [debounced, setDebounced] = useState({})

  const update = useDebounceFn(() => {
    setDebounced({})
  }, options)

  useEffect(() => {
    update()
  }, deps)

  useEffect(effect, [debounced])
}

将 useEffect 改成 useDebounceEffect,就可以实现搜索防抖:

useDebounceEffect(
    () => {
      onSearch(val)
    }
    [val],
    { wait: 500 }
  )

小结

本文实现了 useDebounceFn、useDebounce、useDebounceEffect 3 种防抖 Hooks,这 3 个 Hooks 可以直接下载 ahooks 使用。

参考资料