如何使用 React setState 和 setInterval 循环图像轮播?

IT技术 reactjs setstate
2021-05-09 19:43:43

我正在尝试设置一个图像轮播,当您将鼠标悬停在 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>
  );
}
4个回答

任何涉及计时器/间隔的内容都是 的绝佳候选者useEffect,因为我们可以轻松地在设置计时器的同一位置注册清除操作,使用效果和清理这避免了忘记清除间隔的常见陷阱,例如当组件卸载时,或丢失对间隔句柄的跟踪。请尝试以下操作:

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>
  );
}

编辑饥饿爱因斯坦-d8pe0

至于为什么你的代码不起作用,有几件事:

  1. 你的意思是说if (count === 2) ...,不是count === 3更好的是使用images数组的长度而不是对其进行硬编码
  2. 此外, 的值在闭包内部count陈旧的,即在您使用 更新它之后setCount, 的旧值count仍然在 内部被捕获updateCount这实际上是使用功能状态更新的原因,当您说例如setCount((prevCount) => prevCount + 1)
  3. 您需要在间隔内循环计数,而不是在鼠标悬停时清除间隔。如果你仔细考虑它的逻辑,这应该是显而易见的
  4. 一般来说,在react中,使用函数局部变量timer不会达到你的预期。总是使用状态和效果,在极少数情况下(不是这个),其他一些钩子比如refs

我相信这setInterval不适用于功能组件。由于回调通过闭包访问变量,因此很容易射出自己的脚并获取引用过时值的计时器回调,甚至可以同时运行多个间隔。不是告诉你不能克服那个,但使用setTimeout要容易得多

useEffect(() => {
  if(state === 3) return;
  const timerId = setTimeout(() => setState(old => old + 1), 5000);
  return () => clearTimeout(timerId);
}, [state]);

也许在这种特殊情况下,cleanup( clearTimeout) 不是必需的,但例如,如果用户能够手动切换图像,我们希望延迟下一次自动更改。

  1. 每个渲染周期都会重置计时器引用,将其存储在 React 引用中以使其持续存在。
  2. 初始count状态在区间回调范围内关闭。
  3. 只有 3 张图像,所以最后一张幻灯片将是索引 2,而不是 3。您应该与数组的长度进行比较,而不是对其进行硬编码。
  4. 您可以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>
  );
}

编辑 how-do-i-loop-an-image-carousel-with-react-setstate-and-setinterval

setCount应该使用一个条件来检查它是否应该回到开始:

setCount((prevCount) => prevCount === images.length - 1 ? 0 : prevCount + 1);

这将做setCount(0),如果我们最后的图像,否则,它会做setCount(prevCount + 1)

一种更快(并且可能更具可读性)的方法是:

setCount((prevCount) => (prevCount + 1) % images.length);