【问题标题】:Debouncing (fire function only once in few second) with react hooks用反应钩子去抖动(几秒钟内只有一次触发功能)
【发布时间】:2019-03-10 21:57:56
【问题描述】:

我需要在用户输入上实现标签搜索,但输入速度很快,我不想为用户输入的每个符号触发 DB 调用,所以我是古玩,有没有一种简单的好方法来消除 api 调用让我们比如说 - 延迟 3 秒后一次?

现在我想出了这个:

  let searchDelay
  async function handleTagSearch(e) {
    clearTimeout(searchDelay)

    tagSearchPhraseSet(e.target.value)

    searchDelay = setTimeout(async () => {
      if (e.target.value.length > 3) {
        let res = await fetch('api/tag_seatch/' + e.target.value)
        res = await res.json() 

        console.log(res)
      }
    }, 3000)
  }

但这是正确的方法吗?

【问题讨论】:

  • 看起来是一个可行的解决方案。它在您的应用程序中有效吗?您可能希望将 searchDelay 数字放在 useRef 挂钩变量中,这样它就不会在渲染之间被覆盖。
  • 是的,但我需要以某种方式将 e.target.value 传递给超时函数。我只是不喜欢它;(
  • 您可以将value 放入一个新变量中,这是您在handleTagSearch 中所做的第一件事,而是使用:const { value } = e.target;
  • 当我将它传递给超时时它总是未定义

标签: reactjs react-hooks


【解决方案1】:

如果您确保 searchDelay 数字在渲染之间保持不变,例如,您的解决方案看起来很有希望。 useRef 钩子。

另一种解决方法是使用useEffect 挂钩,每次输入value 更改时都会运行该挂钩。从提供给useEffect 的函数中,您可以返回一个清除上次运行超时的函数。

示例

const { useState, useEffect } = React;

function App() {
  const [value, setValue] = useState("");
  const [result, setResult] = useState(null);

  useEffect(
    () => {
      if (value.length < 3) {
        setResult(null);
        return;
      }

      const timeout = setTimeout(() => {
        setResult(Math.random());
      }, 3000);

      return () => clearTimeout(timeout);
    },
    [value]
  );

  return (
    <div>
      <input value={value} onChange={e => setValue(e.target.value)} />
      <div>{result}</div>
    </div>
  );
}

ReactDOM.render(<App />, document.getElementById("root"));
<script src="https://unpkg.com/react@16/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom@16/umd/react-dom.development.js"></script>

<div id="root"></div>

【讨论】:

    【解决方案2】:

    感谢@Tholle 示例,我了解到 qoute“将从 useEffect 函数返回的函数将被调用 every time it is run again,并按照您所说的卸载”并提出了这个解决方案:

    import React, { useState, useContext, useEffect, useRef } from 'react'
    
    export function TagsAdd() {
      const [searchTerm, searchTermSet] = useState('')
      const isFirstRun = useRef(true)
    
      useEffect(() => {
       //skip first run on component mount
       if (isFirstRun.current) {
          isFirstRun.current = false
          return
       }
    
        const timeout = setTimeout(() => {
          tagSearch(searchTerm)
        }, 2000) //2000 - timeout to execute this function if timeout will be not cleared
    
        return () => clearTimeout(timeout) //clear timeout (delete function execution)
      }, [searchTerm])
    
      // API call only one time in 2 seconds for the last value! Yeeeee
      async function tagSearch(value) {
        let res = await fetch('api/tag_seatch/' + value)
        res = await res.json()
        console.log(res)
      }
    
      //handle input change
      function handleInput(e) {
        searchTermSet(e.target.value)
      }
    
      return (
            <div>
              <input value={searchTerm} onChange={handleInput} />
            </div>
      )
    }
    

    【讨论】:

      【解决方案3】:

      您需要考虑的第一件事是使用useCallback 进行记忆,如果您只是编写一个普通函数,它将在每次重新渲染时重新实例化。 IMO 你应该使用 lodash 的 debounce 函数而不是实现你自己的。结果看起来像这样:

      const searchTags = useCallback(debounce(async evt => {
        const { value } = evt.target;
        if(value.length > 3){
      
          const response = await fetch('/api/tag_search', value);
          const result = await response.json();
      
          setTags(result) //or somewhere in your state
        }
      }, 3000, { trailing: true, leading: false }));
      

      【讨论】:

      • 您确定可以将 useCallback 用于可以更改值的函数吗?
      • 这背后没有什么魔力,useCallback 只是确保你没有在每次重新渲染时重新定义函数,状态可以保存在 useState 钩子上并在回调中更新,这是一个相当常见的用例
      • 我认为 useCallback 用于优化繁重计算。看这篇文章,也许你误会了什么:usehooks.com/useMemo
      猜你喜欢
      • 2020-03-27
      • 2012-12-30
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-08-19
      • 2023-01-10
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多