【问题标题】:missing dependency warning when working with event listeners and states inside useEffect在使用事件侦听器和 useEffect 中的状态时缺少依赖项警告
【发布时间】:2022-01-11 07:54:38
【问题描述】:

每次我使用addEventListener(),并且还想访问useEffect 中的某些状态时,我都会遇到同样的问题。我不能将状态添加为依赖项,因为每次状态更改时我都会创建多个事件侦听器。 我几乎每次都发现自己被“React Hook useEffect has a missing dependency”警告卡住了。 假设我有一个组件需要在window.onClick()window.onDoubleClick() 上更改它的状态。如果状态为真,单击应将其更改为假,如果状态为假,双击应将其更改为真。 所以这就是我要写的:

import React, { useState, useEffect } from 'react';

export default function someComponent() {
  const [toggle, setToggle] = useState(false);

  useEffect(() => {
    window.addEventListener('click', (event) => {
      if (toggle) setToggle(false)
    })

    window.addEventListener('dblclick', (event) => {
      if (!toggle) setToggle(true)
    })
  }, [])

  return (
    <p>The toggle state is {toggle.toString()}</p>
  );
}

此代码有效,但我收到缺少依赖项警告。我无法将切换添加到依赖数组,因为这样每次toggle 状态更改时它都会添加另一个事件侦听器。

我在这里做错了什么?我该如何解决这个问题?

编辑:也许这个例子不太好,但它是我能想到的最简单的。但是,这个问题也适用于当我创建其他事件侦听器时,这些侦听器必须在 windows 对象上,比如滚动。我知道我可以每次都使用 return 来删除事件侦听器,但是对于像滚动这样的事件,它会慢得多。每次我都必须删除并添加它对我来说没有意义,因为我只是不需要它再次触发。

【问题讨论】:

  • 这就是useEffect 依赖数组的工作原理。如果你在useEffect 中访问state,那么它也必须出现在依赖数组中。您可以在useEffect 之外创建一个单独的函数,然后在useEffect 内部调用它。

标签: javascript reactjs event-handling use-effect use-state


【解决方案1】:

在这种情况下,您不必使用 react 元素。甚至没有 useEffect。

通过使用 useEffect 钩子,你告诉 react 在渲染后做一些事情(取决于依赖数组)。在这种情况下,不需要在渲染页面后立即更改状态,只有在用户与元素交互时才需要。

大多数时候可能不需要通过 useEffect 添加点击事件,并且像下面的示例那样执行可能会节省您的时间和头痛,甚至可能会节省性能(如果我错了,请纠正我)。

我个人会这样做。

import React, { useState } from 'react';
        
export default function someComponent() {
  const [toggle, setToggle] = useState(false);

  return (
    <p 
      onClick={() => setToggle(false)} 
      onDoubleClick={() => setToggle(true)}
    >
      The toggle state is {toggle.toString()}
    </p>
  );
}

你也可以像这样从元素中调用函数

  const [toggle, setToggle] = useState(false);

  const handleClick = () => {
    if (toggle) {
      setToggle(false);
    }
  };

  const handleDoubleClick = () => {
    if (!toggle) {
      setToggle(true);
    }
  };

  return (
    <p 
      onClick={() => handleClick()} 
      onDoubleClick={() => handleDoubleClick()}
    >
      The toggle state is {toggle.toString()}
    </p>
  );

代码沙盒示例

【讨论】:

  • CodeSandbox Scroll Example 可能有助于滚动,您应该在使用滚动事件时使用passive-listeners。 useEffect 的主要目标是在某个渲染后给组件指令。使用清理总是好的。这有助于在卸载组件之前整理(清理)您的代码。
  • 我已经使用清理功能进行卸载,但每次滚动都使用它似乎没有必要,而且它需要更多资源,有时速度较慢。我希望 useEffect 在开始时只运行一次。不可能吗?
【解决方案2】:

您可以向 useEffect 挂钩添加清理功能以删除旧的侦听器。这样您就可以将切换传递给依赖数组,并且您不会有堆叠事件侦听器。

https://reactjs.org/docs/hooks-effect.html

  useEffect(() => {
    const handleClick = () => toggle ? setToggle(false) : setToggle(true);

    window.addEventListener('click', handleClick);
    window.addEventListener('dblclick', handleClick);

    return () => {
      window.removeEventListener('click', handleClick);
      window.removeEventListener('dblclick', handleClick);
    }
  }, [toggle]);

【讨论】:

  • 谢谢,但这不是我想要的。我知道清理功能不适合...我编辑了帖子。
【解决方案3】:

我无法将状态添加为依赖项,因为这样每次状态更改时我都会创建多个事件侦听器。

有一种方法可以解决这个问题,那就是从 useEffect 回调到return a cleanup function。我鼓励您阅读文档的链接部分,然后以下解决方案会变得更加清晰:

useEffect(() => {
  const handleClick = () => {
    setToggle(!toggle)
  }

  window.addEventListener('click', handleClick)    

  return () => {
    window.removeEventListener('click', handleClick)
  }
}, [toggle])

使用上面的解决方案,每次更新toggle,都会调用cleanup函数,在再次运行效果之前移除当前的事件监听器。

另外请注意,您可以向setToggle 提供一个回调函数,该函数接收toggle 的当前值并返回新值。使用这种方法,您无需将 toggle 作为依赖项传递给 useEffect:

useEffect(() => {
  const handleClick = () => {
    setToggle(currentValue => !currentValue)
  }
  
  window.addEventListener("click", handleClick)

  return () => {
    window.removeEventListener("click", handleClick)
  }
}, [])

【讨论】:

  • 有时这种解决方案不适合。我现在编辑了帖子。
猜你喜欢
  • 2021-08-13
  • 1970-01-01
  • 2020-11-21
  • 2020-05-06
  • 1970-01-01
  • 2022-04-04
  • 2020-05-28
  • 2021-12-07
  • 1970-01-01
相关资源
最近更新 更多