Reactjs 滑块中的意外 useState 行为

Reactjs unexpected useState behaviour in slider

我使用 useEffect 实现了自动播放滑块(具有三张卡片),但手动“上一个”和“前进”按钮无法正常工作。 useState 没有根据需要更新值。 useState 正在改变我无法理解这种随机行为的值。一旦我点击上一个或向前箭头滑动幻灯片,它就会很快开始更换幻灯片。自动滑块工作正常,但我手动滑动而不是使用状态意外更改值。我怎样才能实现自动和手动(向前和向后箭头)以便 usestate 平滑地更改值。

import React, { useState, useRef, useEffect } from "react";
import { Card, Grid } from "@material-ui/core";
import { makeStyles } from "@material-ui/core/styles";
import { SlideData, SliderImages } from "./SlideData";
import left from "../../../styles/img/homepg/AdmSlider/left.png";
import right from "../../../styles/img/homepg/AdmSlider/right.png";


function NewsSlider() {
  const classes = useStyles();

  const firstRef = useRef(null);
  const secondRef = useRef(null);
  const thirdRef = useRef(null);
  const currentRef = useRef(null);


  const Left = () => {
    setFirst(first <= 0 ? len : first - 1);
    setSecond(second <= 0 ? len : second - 1);
    setThird(third <= 0 ? len : third - 1);
  };

  const Right = () => {
    setFirst(first >= len ? 0 : first + 1);
    setSecond(second >= len ? 0 : second + 1);
    setThird(third >= len ? 0 : third + 1);
  };

  const [first, setFirst] = useState(0);
  const [second, setSecond] = useState(1);
  const [third, setThird] = useState(2);

  const length = SliderImages.length;
  const len = length - 1;
  let timeout;
  
  useEffect(() => {
    setTimeout(() => {
      setFirst(first >= len ? 0 : first + 1);
      setSecond(second >= len ? 0 : second + 1);
      setThird(third >= len ? 0 : third + 1);
       return () => clearTimeout(timeout);
    }, 3000);
  }, [first, second, third]);

  return (
    <>
      <div>
        <Grid container xs={12} className={classes.grid}>
          {" "}
          <div>
            <img
              src={left}
              alt="leftarrow"
              className={classes.left}
              onClick={Left}
            />
          </div>
          <Grid item xs={4} className={classes.card}>
            {SliderImages.map((val, index) => {
              return (
                <div>
                  {index === first && (
                    <Card className={classes.card1}>
                      <img
                        src={val.imgsrc}
                        alt={val.title}
                        className={classes.image}
                        ref={firstRef}
                      />
                    </Card>
                  )}
                  {index === second && (
                    <Card className={classes.card2}>
                      <img
                        src={val.imgsrc}
                        alt={val.title}
                        className={classes.image}
                        ref={secondRef}
                      />
                    </Card>
                  )}
                  {index === third && (
                    <Card className={classes.card3}>
                      <img
                        src={val.imgsrc}
                        alt={val.title}
                        className={classes.image}
                        ref={thirdRef}
                      />
                    </Card>
                  )}
                  <div>
                    <img
                      src={right}
                      alt="rightarrow"
                      className={classes.right}
                      onClick={Right}
                    />
                  </div>
                </div>
              );
            })}
          </Grid>
        </Grid>
      </div>
    </>
  );
}

export default NewsSlider;

问题

您的问题是,当您手动更新 firstsecondthird 之一而未清除任何先前的超时时,您又开始了另一个超时。

此外,当您在函数体中将 timeout 定义为 let timeout; 时,每个渲染周期都会重新声明它。

解决方案

useEffect 回调中捕获超时引用和 return 清除函数,而不是在超时回调中。

useEffect(() => {
  const timeout = setTimeout(() => {
    setFirst(first >= len ? 0 : first + 1);
    setSecond(second >= len ? 0 : second + 1);
    setThird(third >= len ? 0 : third + 1);
  }, 3000);

  return () => clearTimeout(timeout);
}, [first, second, third]);

备选方案

将滑块动画构造为间隔并使用功能状态更新来处理 prev/next 幻灯片。功能状态更新允许您使用间隔计时器并从上一个幻灯片状态正确更新,同时还允许您在不中断间隔更新的情况下异步手动向左或向右滑动。

const Left = () => {
  setFirst(first => first <= 0 ? len : first - 1);
  setSecond(second => second <= 0 ? len : second - 1);
  setThird(third => third <= 0 ? len : third - 1);
};

const Right = () => {
  setFirst(first => first >= len ? 0 : first + 1);
  setSecond(second => second >= len ? 0 : second + 1);
  setThird(third => third >= len ? 0 : third + 1);
};

const [first, setFirst] = useState(0);
const [second, setSecond] = useState(1);
const [third, setThird] = useState(2);

const length = SliderImages.length || 0;
const len = length - 1;

useEffect(() => {
  const interval = setInterval(() => {
    Right();
  }, 3000);

  return () => clearInterval(interval);
}, []);

我认为你需要在 useState 钩子中添加箭头功能

  const Left = () => {
    setFirst(first => first <= 0 ? len : first - 1);
    setSecond(second => second <= 0 ? len : second - 1);
    setThird(third => third <= 0 ? len : third - 1);
  };

  const Right = () => {
    setFirst(first => first >= len ? 0 : first + 1);
    setSecond(second => second >= len ? 0 : second + 1);
    setThird(third => third >= len ? 0 : third + 1);
  };