useEffect中的反跳问题

时间:2020-05-13 22:19:38

标签: reactjs react-hooks debounce

我有一个带有用户名输入的表格,我正在尝试验证用户名是否在反跳功能中正在使用。我遇到的问题是,当我键入“用户”时,我的控制台似乎不起作用,反跳似乎不起作用

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

去抖动功能似乎正确吗?我在反应中如何称呼它有问题吗?

3 个答案:

答案 0 :(得分:15)

每当您的组件重新呈现一个新的去抖动的verify函数时,就会创建该函数,这意味着在useEffect内部实际上您在调用不同的函数,这会破坏去抖动的目的。

这就像您正在做这样的事情一样:

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

解决此问题的一种方法是使用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

答案 1 :(得分:2)

我建议进行一些更改。

1)每次更改状态时,都会触发渲染。每个渲染都有自己的道具和效果。因此,每次您更新用户名时,您的useEffect都会生成一个新的反跳功能。对于useCallback挂钩来说,这是一个很好的案例,可以使渲染之间的函数实例保持相同,或者也许useRef-我自己坚持useCallback。

2)我会分离出单独的处理程序,而不是使用useEffect来触发反跳操作-随着组件的增长,您最终将拥有一长串依赖项,而这并不是最佳选择。

3)您的反跳功能不处理参数。 (我替换为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和hook的出色post

答案 2 :(得分:1)

这里有一个更好的方法来解决这个问题;)

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;
    };
  }, []);
}