提问人:3gwebtrain 提问时间:11/17/2023 最后编辑:Drew Reese3gwebtrain 更新时间:11/18/2023 访问量:117
具有清除和更新时间的自定义钩子无法按预期工作
Custom hook with clear and update time not works as per expected
问:
使用 更新默认时间时,它无法按预期工作。相反,它被添加为新实例。如何清除自定义钩子中的钩子并更新新值?setInterval
setInterval
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;
现场演示 => 请查看控制台
答:
1赞
Drew Reese
11/18/2023
#1
问题
这是回调问题中状态的经典陈旧闭包,但存在一些额外的差异。
timer
在回调中,与初始渲染周期的值相同,因此它总是从相同的值递减。setInterval
- 当 prop 值发生更改时,钩子不会“重置”状态。
useEffect
CustomTimer
timer
startTime
- 挂钩缺少依赖数组,因此它会在第一次更新后的每个渲染周期启动新的超时。
useEffect
App
intervalTime
CustomTimer
实际上是一个自定义的 React 钩子,它应该正确重命名为 .useCustomTimer
溶液
我建议使用 React ref 来保存对当前正在运行的计时器的引用,使用功能状态更新来排队状态更新,并在单独的钩子中检查当前过期。timer
timer
useEffect
例:
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>
);
}
演示
更新以包括在事件发生时重置开始时间"mousemove"
添加事件侦听器来重置状态是微不足道的。可以在 with as 依赖项中添加事件处理程序。timer
"mousemove"
useEffect
startTime
例:
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]);
评论
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"
timer
timer
1赞
Onur Doğan
11/18/2023
#2
结果也可以在这里测试==> 测试演示
首先,需要在文件中添加依赖项,因为每当它触发时,计时器都会从头开始。useEffect
App.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 listener
mousemove
startTime
0赞
3gwebtrain
11/19/2023
它不会重置为以前的值,而是停止计数。请移动鼠标并观察。
0赞
Onur Doğan
11/19/2023
@3gwebtrain我明白了。计数器无法更新,这就是原因。现在,我更新了演示链接,您可以在此处查看:stackblitz.com/edit/stackblitz-starters-skulq6。只是添加了一个新状态,并在触发鼠标移动事件时对其进行更新。此外,需要将其添加到依赖项中以触发计时器restartTimer
useEffect
评论