具有清除和更新时间的自定义钩子无法按预期工作

Custom hook with clear and update time not works as per expected

提问人:3gwebtrain 提问时间:11/17/2023 最后编辑:Drew Reese3gwebtrain 更新时间:11/18/2023 访问量:117

问:

使用 更新默认时间时,它无法按预期工作。相反,它被添加为新实例。如何清除自定义钩子中的钩子并更新新值?setIntervalsetInterval

app.jsx(应用.jsx)

import React from 'react';
import './style.css';
import CustomTimer from './Custom';
import { useState, useEffect } from 'react';

export default function App() {
  const [intervalTime, setIntervalTime] = useState(200);

  const time = CustomTimer(intervalTime);

  useEffect(() => {
    setTimeout(() => {
      console.log('Hi');
      setIntervalTime(500);
    }, 5000);
  });

  return (
    <div className="App">
      <h1>Hello CodeSandbox</h1>
      <h2>Start editing to see some happen! {time} </h2>
    </div>
  );
}

自定义 .js

import { useEffect, useState } from 'react';

function CustomTimer(startTime) {
  const [timer, setTimer] = useState(startTime);
  useEffect(() => {
    const myInterval = setInterval(() => {
      if (timer > 0) {
        setTimer(timer - 1);
        console.log(timer);
      }
    }, 1000);
    return () => clearInterval(myInterval);
  }, [startTime]);
  return timer;
}

export default CustomTimer;

现场演示 => 请查看控制台

javascript reactjs 反应钩子

评论


答:

1赞 Drew Reese 11/18/2023 #1

问题

这是回调问题中状态的经典陈旧闭包,但存在一些额外的差异。

  • timer在回调中,与初始渲染周期的值相同,因此它总是从相同的值递减。setInterval
  • 当 prop 值发生更改时,钩子不会“重置”状态。useEffectCustomTimertimerstartTime
  • 挂钩缺少依赖数组,因此它会在第一次更新后的每个渲染周期启动新的超时。useEffectAppintervalTime
  • CustomTimer实际上是一个自定义的 React 钩子,它应该正确重命名为 .useCustomTimer

溶液

我建议使用 React ref 来保存对当前正在运行的计时器的引用,使用功能状态更新来排队状态更新,并在单独的钩子中检查当前过期。timertimeruseEffect

例:

import { useEffect, useRef, useState } from "react";

function useCustomTimer(startTime) {
  const [timer, setTimer] = useState(startTime);
  const timerRef = useRef();           // <-- ref to hold current timer

  useEffect(() => {
    if (timer === 0) {                 // <-- check timer expiration
      console.log("Timer expired");
      clearInterval(timerRef.current);
    }
  }, [timer])

  useEffect(() => {
    if (startTime > 0) {
      setTimer(startTime);             // <-- reset timer when startTime changes

      timerRef.current = setInterval(
        setTimer,
        1000,
        (timer) => timer - 1           // <-- functional state update
      );
    }

    return () => {
      clearInterval(timerRef.current); // <-- clear on startTime change or unmount
    }
  }, [startTime]);

  return timer;
}
export default function App() {
  const [intervalTime, setIntervalTime] = useState(200);

  const time = useCustomTimer(intervalTime);

  useEffect(() => {
    setTimeout(() => {
      console.log("Hi");
      setIntervalTime(500);
    }, 5000);
  }, []); // <-- add missing dependency array

  return (
    <div className="App">
      <h1>Hello CodeSandbox</h1>
      <h2>Start editing to see some magic happen! {time}</h2>
    </div>
  );
}

演示

Edit custom-hook-with-clear-and-update-time-not-works-as-per-expected

更新以包括在事件发生时重置开始时间"mousemove"

添加事件侦听器来重置状态是微不足道的。可以在 with as 依赖项中添加事件处理程序。timer"mousemove"useEffectstartTime

例:

useEffect(() => {
  if (timerRef.current === 0) {
    console.log("Timer expired");
    clearInterval(timerRef.current);
  }
}, [timer]);

useEffect(() => {
  if (startTime > 0) {
    setTimer(startTime);
    timerRef.current = setInterval(
      setTimer,
      1000,
      (timer) => timer - 1
    );
  }

  // Mousemove callback handler
  const handler = () => {
    setTimer(startTime);
  };

  // Instantiate passive mousemove handler
  document.addEventListener("mousemove", handler, true);

  return () => {
    clearInterval(timerRef.current);
    // Remove mousemove handler when startTime changes
    // or component unmounts
    document.removeEventListener("mousemove", handler, true);
  };
}, [startTime]);

Edit custom-hook-with-clear-and-update-time-not-works-as-per-expected (forked)

评论

0赞 3gwebtrain 11/18/2023
我发现在鼠标移动时重置间隔是一个挑战。你能帮忙吗?
0赞 Drew Reese 11/19/2023
@3gwebtrain 您可以使用钩子添加/删除“mousemove”事件侦听器以更新/重置任何状态。您基本上是在研究不活动计时器吗?对于这篇文章中关于间隔计时器的问题来说,这似乎是课外的,所以最好把它作为一篇包含当前细节的新文章来问,并完成最小的可重复示例。如果您创建了一个新帖子,请随时在此处与我联系并附上链接,我可以在可用时查看。useEffect
0赞 3gwebtrain 11/19/2023
嗨,德鲁,这是演示 codesandbox.io/s/... 请检查鼠标上的控制台。获取不同的值
0赞 Drew Reese 11/21/2023
@3gwebtrain 感谢您的运行沙盒。我在这里更新了我的答案,改进了初始解决方案,并包括一个更新,其中包含了微不足道的更改,以添加事件处理程序来重置状态。如果有机会,请检查一下。Onur 添加另一个状态只是为了重置状态的答案/解决方案倾向于复杂的恕我直言,并且有点 React 反模式。"mousemove"timertimer
1赞 Onur Doğan 11/18/2023 #2

结果也可以在这里测试==> 测试演示

首先,需要在文件中添加依赖项,因为每当它触发时,计时器都会从头开始。useEffectApp.js

App.js:

import React from 'react';
import './style.css';
import CustomTimer from './Custom';
import { useState, useEffect } from 'react';

export default function App() {
  const [intervalTime, setIntervalTime] = useState(200);
  const time = CustomTimer(intervalTime);

  useEffect(() => {
    setTimeout(() => {
      setIntervalTime(500);
    }, 5000);
  }, []);  // Added dependency array here

  return (
    <div className="App">
      <h1>Hello CodeSandbox</h1>
      <h2>Start editing to see some happen! {time} </h2>
    </div>
  );
}

自定义.js:(添加了注释行以解释添加/更新的行)

import { useEffect, useState } from 'react';

function CustomTimer(startTime) {
  const [timer, setTimer] = useState(startTime);

  useEffect(() => {
    setTimer(startTime);
    let timerCounter = startTime;

    const myInterval = setInterval(() => {
      if (timerCounter > 0) {
        // Get the previous state and decrease it 
        setTimer(timer => timer - 1);
        timerCounter--;
        return;
      }

      // Clear interval after timer's work is done
      clearInterval(myInterval);
    }, 1000);

    return () => clearInterval(myInterval);
  }, [startTime]);

  return timer;
}

export default CustomTimer;

评论

0赞 3gwebtrain 11/18/2023
我发现在鼠标移动时重置间隔是一个挑战。你能帮忙吗?
1赞 Onur Doğan 11/19/2023
@3gwebtrain 您可以通过添加 with 方法并将计时器状态添加到侦听器触发时来处理它。我在这里的演示链接中配置了它:stackblitz.com/edit/...。您可以在 Custom.js 文件中看到事件侦听器,我还在那里添加了注释行event listenermousemovestartTime
0赞 3gwebtrain 11/19/2023
它不会重置为以前的值,而是停止计数。请移动鼠标并观察。
0赞 Onur Doğan 11/19/2023
@3gwebtrain我明白了。计数器无法更新,这就是原因。现在,我更新了演示链接,您可以在此处查看:stackblitz.com/edit/stackblitz-starters-skulq6。只是添加了一个新状态,并在触发鼠标移动事件时对其进行更新。此外,需要将其添加到依赖项中以触发计时器restartTimeruseEffect