Skip to content

React Hook 中实现 debounce 防抖函数

假设有如下这么一个需求:有一个输入框,每次输入时会根据 value 去发送请求,需要限制请求频率。

然后使用 react hook,第一次写很容易出错:

js
import _ from 'lodash'
import { useEffect, useState} from 'react'

export function SearchBar() {
  const [searchValue, setSearchValue] = useState('')
  
  useEffect(_.debounce(() => {
      // fetch('https://...')
      
  }, 500), [searchValue])
  
  return (
    <input type="text" value={searchValue} onChange={e => setSearchValue(e.target.value)}></input>
  )
}

然后发现每次 searchValue 改变时,所有请求都是延后 500ms 后触发,并没有做到 debounce。这是因为函数式组件每次渲染时,组件内的变量都会被释放掉重新初始化。

想要做到搜索防抖,可以写一个自定义钩子:

js
export function useDebounceState(state, duration = 1000) {
  const [debounceState, setDebounceState] = useState(state)

  useEffect(() => {
    const timer = setTimeout(() => {
      setDebounceState(state)
    }, duration)
    
    // 每次组件卸载时都会执行返回的这个函数
    return () => clearTimeout(timer)
  }, [state])

  return debounceState
}

然后使用它:

js
export function SearchBar() {
  const [searchValue, setSearchValue] = useState('')
  
  const deboucneValue = useDebounceState(searchValue, 500)
  
  useEffect(() => {
      // fetch('https://...')
      // 第一次这里就会执行
  }, [deboucneValue])
  
  return (
    <input type="text" value={searchValue} onChange={e => setSearchValue(e.target.value)}></input>
  )
}

然后还有两种比较简单的方法,使用 useCallback 钩子或者 useRef 钩子。

useCallback

js
export function SearchBar() {
  const [searchValue, setSearchValue] = useState('')
    
  // 只有当依赖项变化时,返回的函数引用才会改变。因为传入的 [], 所以函数引用永远不会变更。
  const delayFetch = useCallback(_.debounce((param) => {
      // fetch('https://...')
  }, 500), [])
  
  function handleChange(e) {
      setSearchValue(e.target.value)
      delayFetch(e.target.value)
  }
  
  return (
    <input type="text" value={searchValue} onChange={handleChange}></input>
  )
}

useRef 会在函数式组件重新渲染时返回一个相同的 ref 对象。可以通过.current 属性拿到初始值。

js
export function SearchBar() {
  const [searchValue, setSearchValue] = useState('')
    
  const delayFetch = useRef(_.debounce((param) => {
      // fetch('https://...')
  }, 500)).current
  
  function handleChange(e) {
      setSearchValue(e.target.value)
      delayFetch(e.target.value)
  }
  
  return (
    <input type="text" value={searchValue} onChange={handleChange}></input>
  )
}

Released under the MIT License.