如何在 React Hook 中使用节流阀或去抖动?

新手上路,请多包涵

我正在尝试在功能组件中使用来自 lodashthrottle 方法,例如:

 const App = () => {
  const [value, setValue] = useState(0)
  useEffect(throttle(() => console.log(value), 1000), [value])
  return (
    <button onClick={() => setValue(value + 1)}>{value}</button>
  )
}

由于 useEffect 里面的方法在每次渲染时都会重新声明,所以节流效果不起作用。

有没有人有一个简单的解决方案?

原文由 Alexandre Annic 发布,翻译遵循 CC BY-SA 4.0 许可协议

阅读 1.3k
2 个回答

经过一段时间后,我确信使用 setTimeout/clearTimeout (并将其移动到单独的自定义挂钩中)自己处理事情比使用功能帮助程序要容易得多。在我们将其应用于 useCallback 之后,处理后一个会产生额外的挑战,因为依赖关系的变化可以重新创建,但我们不想重置延迟运行。

下面的原始答案

您可能(并且可能需要) useRef 在渲染之间存储值。就像 为计时器建议的那样

类似的东西

const App = () => {
  const [value, setValue] = useState(0)
  const throttled = useRef(throttle((newValue) => console.log(newValue), 1000))

  useEffect(() => throttled.current(value), [value])

  return (
    <button onClick={() => setValue(value + 1)}>{value}</button>
  )
}

至于 useCallback

它也可以作为

const throttled = useCallback(throttle(newValue => console.log(newValue), 1000), []);

但是如果我们尝试重新创建回调一次 value 被更改:

 const throttled = useCallback(throttle(() => console.log(value), 1000), [value]);

我们可能会发现它不会延迟执行:一旦 value 被更改,回调会立即重新创建并执行。

所以我看到 useCallback 在延迟运行的情况下不会提供显着优势。由你决定。

[UPD] 最初是

  const throttled = useRef(throttle(() => console.log(value), 1000))

  useEffect(throttled.current, [value])

但是这样 throttled.current 必须通过关闭来初始 value (0)。因此,即使在下一次渲染中,它也从未改变过。

因此,由于闭包特性,将函数推入 useRef 时要小心。

原文由 skyboyer 发布,翻译遵循 CC BY-SA 4.0 许可协议

我已经创建了自己的自定义挂钩,名为 useDebouncedEffect 它将等待执行 useEffect 直到状态在延迟期间没有更新。

在此示例中,您的效果将在您停止单击按钮 1 秒后记录到控制台。

沙箱示例 https://codesandbox.io/s/react-use-debounced-effect-6jppw

App.jsx

 import { useState } from "react";
import { useDebouncedEffect } from "./useDebouncedEffect";

const App = () => {
  const [value, setValue] = useState(0)

  useDebouncedEffect(() => console.log(value), [value], 1000);

  return (
    <button onClick={() => setValue(value + 1)}>{value}</button>
  )
}

export default App;

useDebouncedEffect.js

 import { useEffect } from "react";

export const useDebouncedEffect = (effect, deps, delay) => {
    useEffect(() => {
        const handler = setTimeout(() => effect(), delay);

        return () => clearTimeout(handler);
    // eslint-disable-next-line react-hooks/exhaustive-deps
    }, [...(deps || []), delay]);
}

禁用 exhaustive-deps 的注释是必需的,除非您想看到警告,因为 lint 将始终抱怨没有作为依赖项生效。添加效果作为依赖项将在每次渲染时触发 useEffect。相反,您可以将检查添加到 useDebouncedEffect 以确保它已通过所有依赖项。 (见下文)

将详尽的依赖项检查添加到 useDebouncedEffect

如果你想让 eslint 检查 useDebouncedEffect 以获取详尽的依赖项,你可以将它添加到 eslint 配置中 package.json

   "eslintConfig": {
    "extends": [
      "react-app"
    ],
    "rules": {
      "react-hooks/exhaustive-deps": ["warn", {
        "additionalHooks": "useDebouncedEffect"
      }]
    }
  },

https://github.com/facebook/react/tree/master/packages/eslint-plugin-react-hooks#advanced-configuration

原文由 Todd Skelton 发布,翻译遵循 CC BY-SA 4.0 许可协议

撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进