`useState`的值在`addEventListener`中使用,但是使用`setInterval`后没有更新。

huangapple go评论71阅读模式
英文:

useState value used setInterval is not updating in addEventListener

问题

I'm trying to pass the value to addEventListener(mousemove) from setInterval. Somehow it's not working properly. Can someone help?

  const [date, setDate] = useState(new Date());

  function refreshClock() {
    setDate(new Date());
  }

  var x = 0;
  var y = 0;

  const [text, setText] = useState('');

  let windowHeight = window.innerHeight/2;
  let windowWidth = window.innerWidth/2;

  useEffect(() => {
    const timerId = setInterval(refreshClock, 1000);  

    const onMouseMove = function(e) {
      x = e.clientX;
      y = e.clientY;

      if (x < windowWidth + 100 && x > windowWidth - 100 && y < windowHeight + 100 && y > windowHeight - 100) {
        setText(`${date.toLocaleTimeString('en-GB')}`);
      } else {
        setText('');
      }
    }

    window.addEventListener("mousemove", onMouseMove)

    return function cleanup() {
      clearInterval(timerId);
      window.removeEventListener("mousemove", onMouseMove)
    };
  }, []);
  
  return (
    <>
      {text}
    </>
  )

I'm trying to make time show when the mouse is around the center of the window. setInterval is working fine without addEventListener.

英文:

I'm trying to pass the value to addEventListener(mousemove) from setInterval. Somehow it's not working properly. Can someone help?

  const [date, setDate] = useState(new Date());

  function refreshClock() {
    setDate(new Date());
  }

  var x = 0;
  var y = 0;

  const [text, setText] = useState(&#39;&#39;);
  
  let windowHeight = window.innerHeight/2;
  let windowWidth = window.innerWidth/2;
  
  useEffect(() =&gt; {
    const timerId = setInterval(refreshClock, 1000);  

    const onMouseMove = function(e) {
      x = e.clientX;
      y = e.clientY;

      if (x &lt; windowWidth+100 &amp;&amp; x &gt; windowWidth-100 &amp;&amp; y &lt; windowHeight+100 &amp;&amp; y &gt; windowHeight-100 ) {
        setText(`${date.toLocaleTimeString(&#39;en-GB&#39;)}`);
      } else {
        setText(&#39;&#39;);
      }
    }

    window.addEventListener(&quot;mousemove&quot;, onMouseMove)

    return function cleanup() {
      clearInterval(timerId);
      window.removeEventListener(&quot;mousemove&quot;, onMouseMove)
    };
  }, []);
  
  return (
    &lt;&gt;
      {text}
    &lt;/&gt;
  )

I'm trying to make time shows when the mouse is around the center of the window. setInterval is working fine without addEventListener.

答案1

得分: 2

以下是您要翻译的内容:

"您目前遇到的主要问题是 -

  1. 您的 useEffect 仅运行一次
  2. onMouseMove 在效果内声明
  3. onMouseMove 封闭在 date
  4. date 将始终相同,因为 onMouseMove 从未获得更新的值
  5. 因此,setText 仅显示第一个 date

您遇到的另一个问题是 -

  1. 窗口宽度/高度在效果外计算一次
  2. onMouseMove 封闭在这些值上
  3. 如果窗口大小调整,事件侦听器将保留旧值

我们可以通过将 onMouseMove 移出效果来解决这些问题。而不是将 date 状态复制到 text,我们将使用 useRef 来简单地切换可见性开/关。在React中,这被称为保持 单一真相来源。以下是一个最小的可重现示例 -

<!-- begin snippet: js hide: false console: true babel: true -->

<!-- language: lang-js -->
function App() {
  const [date, setDate] = React.useState(new Date())
  const isDateVisible = React.useRef(false)
  
  const refreshClock = () =>
    setDate(new Date()) // 更新时钟
    
  const onMouseMove = event => {
    const wx = window.innerWidth/2
    const wy = window.innerHeight/2
    const x = event.clientX
    const y = event.clientY
    isDateVisible.current = // 更新可见性
      x < wx+100 && x > wx-100 && y < wy+100 && y > wy-100
  }
  
  React.useEffect(
    () => {
      const t = setInterval(refreshClock, 1000)
      addEventListener("mousemove", onMouseMove)
      return () => {
        clearInterval(t)
        removeEventListener("mousemove", onMouseMove)
      }
    },
    [] // refreshClock 和 onMouseMove 没有依赖项
  )
  
  return isDateVisible.current
    && date.toLocaleTimeString('en-GB')
}

ReactDOM.createRoot(document.querySelector("#app")).render(<App />)

<!-- language: lang-css -->
body::before { content: "将光标移动到中心"; }

<!-- language: lang-html -->
<script crossorigin src="https://unpkg.com/react@18/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom@18/umd/react-dom.development.js"></script>
<div id="app"></div>

<!-- end snippet -->

严格的依赖关系

一般来说,您需要将任何自由变量作为效果的依赖项包括进去。如果我们要严格一点,refreshClockonMouseMove 将被列为依赖项,但它们自己没有依赖项,因此可以安全地排除它们。

refreshClockonMouseMove 每次 date 状态更改并重新渲染组件时都会被重新创建。如果将它们列为 useEffect 的依赖项,那么效果也会重新运行,这是我们希望避免的。

使用 useCallback 可确保 refreshClockonMouseMove 将指向相同的函数对象,以防止不必要地重新运行效果。这个更新后的程序与上面的程序具有相同的行为,但更加严格,可能更清晰地传达了意图。

<!-- begin snippet: js hide: false console: true babel: true -->

<!-- language: lang-js -->
function App() {
  const [date, setDate] = React.useState(new Date())
  const isDateVisible = React.useRef(false)
  
  const refreshClock = React.useCallback(
    () => setDate(new Date()),
    [] // 零依赖
  )
    
  const onMouseMove = React.useCallback(
    event => {
      const wx = window.innerWidth/2
      const wy = window.innerHeight/2
      const x = event.clientX
      const y = event.clientY
      isDateVisible.current =
        x < wx+100 && x > wx-100 && y < wy+100 && y > wy-100
    },
    [] // 零依赖
  )
  
  React.useEffect(
    () => {
      const t = setInterval(refreshClock, 1000)
      addEventListener("mousemove", onMouseMove)
      return () => {
        clearInterval(t)
        removeEventListener("mousemove", onMouseMove)
      }
    },
    [refreshClock, onMouseMove] // 严格依赖
  )
  
  return isDateVisible.current
    && date.toLocaleTimeString('en-GB')
}

ReactDOM.createRoot(document.querySelector("#app")).render(<App />)

<!-- language: lang-css -->
body::before { content: "将光标移动到中心"; }

<!-- language: lang-html -->
<script crossorigin src="https://unpkg.com/react@18/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom@18/umd/react-dom.development.js"></script>
<div id="app"></div>

<!-- end snippet -->

最后说明:

  1. 不要将状态设置函数包括为依赖项。setDate 已经保证是来自 useState 的不变值
  2. 不要将引用包括为依赖项。引用不代表响应式状态更改,因此不会触发渲染。"
英文:

The main issue you are having is that -

  1. your useEffect runs only once
  2. onMouseMove is declared inside the effect
  3. onMouseMove closes over date
  4. date will always be the same because the onMouseMove never gets an updated value
  5. setText therefore will only display the first date

Another issue you are having is -

  1. window width/height are computed once outside the effect
  2. onMouseMove closes over these values
  3. if the window is resized, the event listener will keep the old values

We can fix these issues by moving onMouseMove outside the effect. And instead of copying date state to text, we will use useRef to simply toggle the visibility on/off. In react this is known as keeping a single source of truth. Here's a minimal reproducible example -

<!-- begin snippet: js hide: false console: true babel: true -->

<!-- language: lang-js -->

function App() {
  const [date, setDate] = React.useState(new Date())
  const isDateVisible = React.useRef(false)
  
  const refreshClock = () =&gt;
    setDate(new Date()) // update clock
    
  const onMouseMove = event =&gt; {
    const wx = window.innerWidth/2
    const wy = window.innerHeight/2
    const x = event.clientX
    const y = event.clientY
    isDateVisible.current = // update visibility
      x &lt; wx+100 &amp;&amp; x &gt; wx-100 &amp;&amp; y &lt; wy+100 &amp;&amp; y &gt; wy-100
  }
  
  React.useEffect(
    () =&gt; {
      const t = setInterval(refreshClock, 1000)
      addEventListener(&quot;mousemove&quot;, onMouseMove)
      return () =&gt; {
        clearInterval(t)
        removeEventListener(&quot;mousemove&quot;, onMouseMove)
      }
    },
    [] // refreshClock and onMouseMove have zero deps
  )
  
  return isDateVisible.current
    &amp;&amp; date.toLocaleTimeString(&#39;en-GB&#39;)
}

ReactDOM.createRoot(document.querySelector(&quot;#app&quot;)).render(&lt;App /&gt;)

<!-- language: lang-css -->

body::before { content: &quot;move the cursor to the center&quot;; }

<!-- language: lang-html -->

&lt;script crossorigin src=&quot;https://unpkg.com/react@18/umd/react.development.js&quot;&gt;&lt;/script&gt;
&lt;script crossorigin src=&quot;https://unpkg.com/react-dom@18/umd/react-dom.development.js&quot;&gt;&lt;/script&gt;
&lt;div id=&quot;app&quot;&gt;&lt;/div&gt;

<!-- end snippet -->

strict dependencies

In general, you will need to include any free variables as dependencies to your effects. If we were to be strict, refreshClock and onMouseMove would be listed as dependencies, but they have zero dependencies of their own and so it's safe to leave them out.

refreshClock and onMouseMove are recreated each time date state changes and the component is re-rendered. If we list them as dependencies to useEffect then the effect will re-run too, which we'd like to avoid.

Using useCallback ensures that refreshClock and onMouseMove will point to the same function object for each render, and prevents the effect from re-running unnecessarily. This updated program has the same behaviour as the one above, but it is more strict, and possibly communicates intentions more clearly.

<!-- begin snippet: js hide: false console: true babel: true -->

<!-- language: lang-js -->

function App() {
  const [date, setDate] = React.useState(new Date())
  const isDateVisible = React.useRef(false)
  
  const refreshClock = React.useCallback(
    () =&gt; setDate(new Date()),
    [] // zero dependencies
  )
    
  const onMouseMove = React.useCallback(
    event =&gt; {
      const wx = window.innerWidth/2
      const wy = window.innerHeight/2
      const x = event.clientX
      const y = event.clientY
      isDateVisible.current =
        x &lt; wx+100 &amp;&amp; x &gt; wx-100 &amp;&amp; y &lt; wy+100 &amp;&amp; y &gt; wy-100
    },
    [] // zero dependencies
  )
  
  React.useEffect(
    () =&gt; {
      const t = setInterval(refreshClock, 1000)
      addEventListener(&quot;mousemove&quot;, onMouseMove)
      return () =&gt; {
        clearInterval(t)
        removeEventListener(&quot;mousemove&quot;, onMouseMove)
      }
    },
    [refreshClock, onMouseMove] // strict dependencies
  )
  
  return isDateVisible.current
    &amp;&amp; date.toLocaleTimeString(&#39;en-GB&#39;)
}

ReactDOM.createRoot(document.querySelector(&quot;#app&quot;)).render(&lt;App /&gt;)

<!-- language: lang-css -->

body::before { content: &quot;move the cursor to the center&quot;; }

<!-- language: lang-html -->

&lt;script crossorigin src=&quot;https://unpkg.com/react@18/umd/react.development.js&quot;&gt;&lt;/script&gt;
&lt;script crossorigin src=&quot;https://unpkg.com/react-dom@18/umd/react-dom.development.js&quot;&gt;&lt;/script&gt;
&lt;div id=&quot;app&quot;&gt;&lt;/div&gt;

<!-- end snippet -->

Final notes:

  1. Do not include state setter function as dependency. setDate is already guaranteed to be a non-changing value from useState
  2. Do not include references as dependencies. Refs do not represent reactive state change and therefore do not trigger renders.

huangapple
  • 本文由 发表于 2023年2月14日 21:01:23
  • 转载请务必保留本文链接:https://go.coder-hub.com/75448216.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定