【问题标题】:Problems with debounce in useEffectuseEffect 中的 debounce 问题
【发布时间】:2020-08-30 07:28:33
【问题描述】:

我有一个带有用户名输入的表单,我正在尝试验证用户名是否正在使用中或没有在去抖功能中。我遇到的问题是我的 debounce 似乎不起作用,因为当我输入“user”时,我的控制台看起来像

u
us
use
user

这是我的去抖动功能

export function debounce(func, wait, immediate) {
    var timeout;

    return () => {
        var context = this, args = arguments;

        var later = () => {
            timeout = null;
            if (!immediate) func.apply(context, args);
        };

        var callNow = immediate && !timeout;

        clearTimeout(timeout);

        timeout = setTimeout(later, wait);

        if (callNow) func.apply(context, args);
    };
};

这是我在 React 组件中的调用方式

import React, { useEffect } from 'react' 

// verify username
useEffect(() => {
    if(state.username !== "") {
        verify();
    }
}, [state.username])

const verify = debounce(() => {
    console.log(state.username)
}, 1000);

去抖动功能似乎是正确的?我在 react 中的调用方式有问题吗?

【问题讨论】:

    标签: reactjs react-hooks debounce


    【解决方案1】:

    每次您的组件重新渲染时,都会创建一个去抖动的verify 函数,这意味着在useEffect 内部,您实际上是在调用不同的函数,这违背了去抖动的目的。

    就像你在做这样的事情:

    const debounced1 = debounce(() => { console.log(state.username) }, 1000);
    debounced1();
    
    const debounced2 = debounce(() => { console.log(state.username) }, 1000);
    debounced2();
    
    const debounced3 = debounce(() => { console.log(state.username) }, 1000);
    debounced3();
    

    与你真正想要的相反:

    const debounced = debounce(() => { console.log(state.username) }, 1000);
    debounced();
    debounced();
    debounced();
    

    解决这个问题的一种方法是使用useCallback,它总是返回相同的回调(当你传入一个空数组作为第二个参数时),另外,我会将username传递给这个函数而不是访问内部状态(否则您将访问陈旧状态):

    import { useCallback } from "react";
    const App => () {
      const [username, setUsername] = useState("");
    
      useEffect(() => {
        if (username !== "") {
          verify(username);
        }
      }, [username]);
    
      const verify = useCallback(
        debounce(name => {
          console.log(name);
        }, 200),
        []
      );
    
      return <input onChange={e => setUsername(e.target.value)} />;
    }
    

    您还需要稍微更新您的去抖函数,因为它没有将参数正确传递给去抖函数。

    function debounce(func, wait, immediate) {
      var timeout;
    
      return (...args) => { <--- needs to use this `args` instead of the ones belonging to the enclosing scope
        var context = this;
    ...
    

    demo

    【讨论】:

    • 感谢完美修复并再次感谢您解释它是有道理的
    • 效果很好!谢谢:)
    • 我还没有在互联网上找到的最简洁的解释。太棒了!
    【解决方案2】:

    我建议进行一些更改。

    1) 每次进行状态更改时,都会触发渲染。每个渲染都有自己的道具和效果。因此,您的useEffect 每次更新用户名时都会生成一个新的去抖动功能。这是 useCallback 钩子在渲染之间保持函数实例相同的好案例,或者可能是 useRef - 我自己坚持使用 useCallback。

    2) 我会分离出单独的处理程序,而不是使用 useEffect 来触发您的去抖动 - 随着组件的增长,您最终会得到一长串依赖项,这不是最好的地方。

    3) 你的 debounce 函数不处理参数。 (我换成了lodash.debouce,不过你可以调试你的实现)

    4)我认为您仍然想更新按键状态,但仅每 x 秒运行一次您的谴责功能

    例子:

    import React, { useState, useCallback } from "react";
    import "./styles.css";
    import debounce from "lodash.debounce";
    
    export default function App() {
      const [username, setUsername] = useState('');
    
      const verify = useCallback(
        debounce(username => {
          console.log(`processing ${username}`);
        }, 1000),
        []
      );
    
      const handleUsernameChange = event => {
        setUsername(event.target.value);
        verify(event.target.value);
      };
    
      return (
        <div className="App">
          <h1>Debounce</h1>
          <input type="text" value={username} onChange={handleUsernameChange} />
        </div>
      );
    }
    

    DEMO

    我强烈推荐阅读这篇关于 useEffect 和 hooks 的精彩 post

    【讨论】:

    • 感谢您提供的优质资源!
    • 是否建议分手 [state, setState] = { step: 1, name: "", email: "", username: "", password: "", confirm: "", error :“”,已验证:“”,有效:false } 进入单独的 useStates?我明白为什么要这样做来处理用户名和去抖动。
    • 是的,我会将状态和相关处理程序分开,以便您拥有[username, setUsername] handleUsernameChange() [password, setPassword] handlePasswordChange()
    【解决方案3】:

    这是解决此问题的更好方法;)

    export function useLazyEffect(effect: EffectCallback, deps: DependencyList = [], wait = 300) {
      const cleanUp = useRef<void | (() => void)>();
      const effectRef = useRef<EffectCallback>();
      const updatedEffect = useCallback(effect, deps);
      effectRef.current = updatedEffect;
      const lazyEffect = useCallback(
        _.debounce(() => {
          cleanUp.current = effectRef.current?.();
        }, wait),
        [],
      );
      useEffect(lazyEffect, deps);
      useEffect(() => {
        return () => {
          cleanUp.current instanceof Function ? cleanUp.current() : undefined;
        };
      }, []);
    }
    

    【讨论】:

    • 如果只有人们在滚动到这一点来支持 xD
    猜你喜欢
    • 2019-12-24
    • 2022-01-22
    • 2020-08-09
    • 2021-11-13
    • 2021-09-26
    • 1970-01-01
    • 2018-04-12
    • 2021-05-07
    • 2021-04-21
    相关资源
    最近更新 更多