如何使用 React setState 和 setInterval 循环图像轮播?
How do I loop an image carousel with React setState and setInterval?
我正在尝试设置一个图像轮播,当您将鼠标悬停在 div 上时,它会循环播放 3 张图像。我在尝试弄清楚如何在到达第三张图像后重置循环时遇到问题。我需要重置 setInterval 以便它再次启动并在您将鼠标悬停在 div 上时连续循环显示图像。然后当你鼠标移出 div 时,循环需要停止并重置为初始状态 0。这是代码沙箱:
https://codesandbox.io/s/pedantic-lake-wn3s7
import React, { useState, useEffect } from "react";
import { images } from "./Data";
import "./styles.css";
export default function App() {
let timer;
const [count, setCount] = useState(0);
const updateCount = () => {
timer = setInterval(() => {
setCount((prevCount) => prevCount + 1);
}, 1000);
if (count === 3) clearInterval(timer);
};
const origCount = () => {
clearInterval(timer);
setCount((count) => 0);
};
return (
<div className="App">
<div className="title">Image Rotate</div>
<div onMouseOver={updateCount} onMouseOut={origCount}>
<img src={images[count].source} alt={images.name} />
<p>count is: {count}</p>
</div>
</div>
);
}
你的 setCount
应该使用一个条件来检查它是否应该回到开始:
setCount((prevCount) => prevCount === images.length - 1 ? 0 : prevCount + 1);
如果我们在最后一张图片上,这将执行 setCount(0)
— 否则,它将执行 setCount(prevCount + 1)
。
一种更快(并且可能更具可读性)的方法是:
setCount((prevCount) => (prevCount + 1) % images.length);
- 计时器引用在每个渲染周期重置,将其存储在 React 引用中以使其持续存在。
- 初始
count
状态在区间回调范围内结束。
- 只有 3 张图片,因此最后一张幻灯片的索引为 2,而不是 3。您应该与数组的长度进行比较,而不是对其进行硬编码。
- 您可以通过数组长度对
count
状态取模来计算图像索引。
代码:
export default function App() {
const timerRef = useRef();
const [count, setCount] = useState(0);
// clear any running intervals when unmounting
useEffect(() => () => clearInterval(timerRef.current), []);
const updateCount = () => {
timerRef.current = setInterval(() => {
setCount((count) => count + 1);
}, 1000);
};
const origCount = () => {
clearInterval(timerRef.current);
setCount(0);
};
return (
<div className="App">
<div className="title">Image Rotate</div>
<div onMouseOver={updateCount} onMouseOut={origCount}>
<img
src={images[count % images.length].source} // <-- computed index to cycle
alt={images.name}
/>
<p>count is: {count}</p>
</div>
</div>
);
}
任何涉及 timers/intervals 的内容都是 useEffect
, because we can easily register a clear action in the same place that we set the timer using effects with cleanup 的绝佳候选。这避免了忘记清除间隔的常见陷阱,例如当组件卸载时,或失去对间隔句柄的跟踪时。尝试如下操作:
import React, { useState, useEffect } from "react";
import { images } from "./Data";
import "./styles.css";
export default function App() {
const [count, setCount] = useState(0);
const [mousedOver, setMousedOver] = useState(false);
useEffect(() => {
// set an interval timer if we are currently moused over
if (mousedOver) {
const timer = setInterval(() => {
// cycle prevCount using mod instead of checking for hard-coded length
setCount((prevCount) => (prevCount + 1) % images.length);
}, 1000);
// automatically clear timer the next time this effect is fired or
// the component is unmounted
return () => clearInterval(timer);
} else {
// otherwise (not moused over), reset the counter
setCount(0);
}
// the dependency on mousedOver means that this effect is fired
// every time mousedOver changes
}, [mousedOver]);
return (
<div className="App">
<div className="title">Image Rotate</div>
<div
// just set mousedOver here instead of calling update/origCount
onMouseOver={() => setMousedOver(true)}
onMouseOut={() => setMousedOver(false)}
>
<img src={images[count].source} alt={images.name} />
<p>count is: {count}</p>
</div>
</div>
);
}
关于为什么您的代码不起作用,有几点:
- 您的意思是
if (count === 2) ...
,而不是 count === 3
。更好的方法是使用 images
数组的长度而不是对其进行硬编码
- 此外,
count
的值是 stale inside of the closure,即在您使用 setCount
更新它之后,count
的旧值仍然在 updateCount
。这实际上是使用功能状态更新的原因,当你说例如setCount((prevCount) => prevCount + 1)
- 您需要在间隔内循环计数,而不是在鼠标悬停时清除间隔。如果你仔细考虑它的逻辑,这应该是显而易见的
- 一般来说,在 React 中,使用像
timer
这样的函数局部变量不会达到您的预期。总是使用状态和效果,在极少数情况下(不是这个),一些其他的钩子,比如 refs
我认为 setInterval
不适用于函数组件。由于回调通过闭包访问变量,因此很容易出事,要么得到引用陈旧值的定时器回调,要么甚至同时有多个间隔 运行。不是说你无法克服它,而是使用 setTimeout
更容易使用
useEffect(() => {
if(state === 3) return;
const timerId = setTimeout(() => setState(old => old + 1), 5000);
return () => clearTimeout(timerId);
}, [state]);
也许在这种特殊情况下不需要清理(clearTimeout
),但是例如如果用户能够手动切换图像,我们希望延迟下一次自动更改。
我正在尝试设置一个图像轮播,当您将鼠标悬停在 div 上时,它会循环播放 3 张图像。我在尝试弄清楚如何在到达第三张图像后重置循环时遇到问题。我需要重置 setInterval 以便它再次启动并在您将鼠标悬停在 div 上时连续循环显示图像。然后当你鼠标移出 div 时,循环需要停止并重置为初始状态 0。这是代码沙箱:
https://codesandbox.io/s/pedantic-lake-wn3s7
import React, { useState, useEffect } from "react";
import { images } from "./Data";
import "./styles.css";
export default function App() {
let timer;
const [count, setCount] = useState(0);
const updateCount = () => {
timer = setInterval(() => {
setCount((prevCount) => prevCount + 1);
}, 1000);
if (count === 3) clearInterval(timer);
};
const origCount = () => {
clearInterval(timer);
setCount((count) => 0);
};
return (
<div className="App">
<div className="title">Image Rotate</div>
<div onMouseOver={updateCount} onMouseOut={origCount}>
<img src={images[count].source} alt={images.name} />
<p>count is: {count}</p>
</div>
</div>
);
}
你的 setCount
应该使用一个条件来检查它是否应该回到开始:
setCount((prevCount) => prevCount === images.length - 1 ? 0 : prevCount + 1);
如果我们在最后一张图片上,这将执行 setCount(0)
— 否则,它将执行 setCount(prevCount + 1)
。
一种更快(并且可能更具可读性)的方法是:
setCount((prevCount) => (prevCount + 1) % images.length);
- 计时器引用在每个渲染周期重置,将其存储在 React 引用中以使其持续存在。
- 初始
count
状态在区间回调范围内结束。 - 只有 3 张图片,因此最后一张幻灯片的索引为 2,而不是 3。您应该与数组的长度进行比较,而不是对其进行硬编码。
- 您可以通过数组长度对
count
状态取模来计算图像索引。
代码:
export default function App() {
const timerRef = useRef();
const [count, setCount] = useState(0);
// clear any running intervals when unmounting
useEffect(() => () => clearInterval(timerRef.current), []);
const updateCount = () => {
timerRef.current = setInterval(() => {
setCount((count) => count + 1);
}, 1000);
};
const origCount = () => {
clearInterval(timerRef.current);
setCount(0);
};
return (
<div className="App">
<div className="title">Image Rotate</div>
<div onMouseOver={updateCount} onMouseOut={origCount}>
<img
src={images[count % images.length].source} // <-- computed index to cycle
alt={images.name}
/>
<p>count is: {count}</p>
</div>
</div>
);
}
任何涉及 timers/intervals 的内容都是 useEffect
, because we can easily register a clear action in the same place that we set the timer using effects with cleanup 的绝佳候选。这避免了忘记清除间隔的常见陷阱,例如当组件卸载时,或失去对间隔句柄的跟踪时。尝试如下操作:
import React, { useState, useEffect } from "react";
import { images } from "./Data";
import "./styles.css";
export default function App() {
const [count, setCount] = useState(0);
const [mousedOver, setMousedOver] = useState(false);
useEffect(() => {
// set an interval timer if we are currently moused over
if (mousedOver) {
const timer = setInterval(() => {
// cycle prevCount using mod instead of checking for hard-coded length
setCount((prevCount) => (prevCount + 1) % images.length);
}, 1000);
// automatically clear timer the next time this effect is fired or
// the component is unmounted
return () => clearInterval(timer);
} else {
// otherwise (not moused over), reset the counter
setCount(0);
}
// the dependency on mousedOver means that this effect is fired
// every time mousedOver changes
}, [mousedOver]);
return (
<div className="App">
<div className="title">Image Rotate</div>
<div
// just set mousedOver here instead of calling update/origCount
onMouseOver={() => setMousedOver(true)}
onMouseOut={() => setMousedOver(false)}
>
<img src={images[count].source} alt={images.name} />
<p>count is: {count}</p>
</div>
</div>
);
}
关于为什么您的代码不起作用,有几点:
- 您的意思是
if (count === 2) ...
,而不是count === 3
。更好的方法是使用images
数组的长度而不是对其进行硬编码 - 此外,
count
的值是 stale inside of the closure,即在您使用setCount
更新它之后,count
的旧值仍然在updateCount
。这实际上是使用功能状态更新的原因,当你说例如setCount((prevCount) => prevCount + 1)
- 您需要在间隔内循环计数,而不是在鼠标悬停时清除间隔。如果你仔细考虑它的逻辑,这应该是显而易见的
- 一般来说,在 React 中,使用像
timer
这样的函数局部变量不会达到您的预期。总是使用状态和效果,在极少数情况下(不是这个),一些其他的钩子,比如 refs
我认为 setInterval
不适用于函数组件。由于回调通过闭包访问变量,因此很容易出事,要么得到引用陈旧值的定时器回调,要么甚至同时有多个间隔 运行。不是说你无法克服它,而是使用 setTimeout
更容易使用
useEffect(() => {
if(state === 3) return;
const timerId = setTimeout(() => setState(old => old + 1), 5000);
return () => clearTimeout(timerId);
}, [state]);
也许在这种特殊情况下不需要清理(clearTimeout
),但是例如如果用户能够手动切换图像,我们希望延迟下一次自动更改。