React Hook中实现debounce防抖函数

456 阅读1分钟

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

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

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。这是因为函数式组件每次渲染时,组件内的变量都会被释放掉重新初始化。

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

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

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

  return debounceState
}

然后使用它:

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

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属性拿到初始值。

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>
  )
}