反应-useState不会更新setInterval上的状态

时间:2019-12-02 18:13:25

标签: reactjs

即使我将其设置为oldvalue + 1,状态也不会更新。 注销ltrNewValuertlNewValue的值时,它总是相同的。就是因为它被初始状态所覆盖。

const Row = (props) => {
  const [rowState, setRowState] = useState({
    renderInterval: null,
    value: 0,
  });

  useEffect(() => {
    const interval = setInterval(counterIntervalFunction, props.speed);
    setRowState({ ...rowState, renderInterval: interval });
  }, []);

  const counterIntervalFunction = () => {
    if (props.isRunning && props.direction === 'ltr') {
      const ltrNewValue = rowState.value === 2 ? 0 : rowState.value + 1;
      console.log(ltrNewValue); // always 1
       setRowState({ ...rowState, value: ltrNewValue });
      console.log(rowState.value); // always 0
      props.setRotatingValue(props.index, rowState.value);
    } else if (props.isRunning && props.direction === 'rtl') {
      const rtlNewValue = rowState.value === 0 ? 2 : rowState.value - 1;
      setRowState({ ...rowState, value: rtlNewValue });
      props.setRotatingValue(props.index, rowState.value);
    } else {
      clearCounterInterval();
    }
  };

我的最终目标是将rowState.value递增到2,然后在无限循环中将其设置为0。如何正确执行此操作?

2 个答案:

答案 0 :(得分:2)

我不确定,但看来您在这里过时的回调有问题。

 useEffect(() => {
    const interval = setInterval(counterIntervalFunction, props.speed);
    setRowState({ ...rowState, renderInterval: interval });
  }, []);

此效果仅运行一次-首次安装组件时。该间隔使用counterIntervalFunction函数:

  const counterIntervalFunction = () => {
    if (props.isRunning && props.direction === 'ltr') {
      const ltrNewValue = rowState.value === 2 ? 0 : rowState.value + 1;
      console.log(ltrNewValue); // always 1
       setRowState({ ...rowState, value: ltrNewValue });
      console.log(rowState.value); // always 0
      props.setRotatingValue(props.index, rowState.value);
    } else if (props.isRunning && props.direction === 'rtl') {
      const rtlNewValue = rowState.value === 0 ? 2 : rowState.value - 1;
      setRowState({ ...rowState, value: rtlNewValue });
      props.setRotatingValue(props.index, rowState.value);
    } else {
      clearCounterInterval();
    }
  };

counterIntervalFunction捕获props的引用,并使用它来确定向用户显示的内容。但是,因为该函数仅在安装组件时运行,因此该事件仅在将道具原先传递给该函数的情况下运行!您可以在this codesandbox.io

中看到此情况的示例

这就是为什么您应该将所有外部依赖项放入依赖项数组的原因:

useEffect(() => {
  const interval = setInterval(counterIntervalFunction, props.speed); 
  setRowState({ ...rowState, renderInterval: interval }); 
}, [counterIntervalFunction, props.speed, rowState]);

但是,这将导致无限循环。

useEffect中设置状态通常被认为是一个坏主意,因为它会导致无限循环-更改状态将导致组件重新渲染,并引发其他效果等。

查看效果循环,您实际上感兴趣的是捕获对间隔的引用。如果更改此间隔,则实际上不会对组件产生任何影响,因此我们可以使用引用来跟踪它,而不是使用状态。引用不会重新呈现。这也意味着我们可以将value更改为独立值。

因为我们现在不再依赖rowState,所以我们可以从依赖关系数组中删除它,从而防止无限渲染。现在,我们的效果仅取决于props.speedcounterIntervalFunction

  const renderInterval = React.useRef();
  const [value, setValue] = React.useState(0);

  useEffect(() => {
    renderInterval.current = setInterval(counterIntervalFunction, props.speed);
    return () => {
      cancelInterval(renderInterval.current);
    };
  }, [props.speed, counterIntervalFunction]);

这将起作用,但是由于counterIntervalFunction是内联定义的,因此将在每个渲染中重新创建它,从而导致效果触发每个渲染。我们可以使用React.useCallback()来稳定它。我们还将要添加此函数的所有依赖关系,以确保不捕获对道具的陈旧引用,并且可以将setRowState更改为setValue。最后,由于间隔已被useEffect取消,因此我们不再需要调用clearCounterInterval

  const counterIntervalFunction = React.useCallback(() => {
    if (props.isRunning && props.direction === 'ltr') {
      const ltrNewValue = value === 2 ? 0 : value + 1;
      setValue(ltrNewValue);
      props.setRotatingValue(props.index, ltrNewValue);
    } else if (isRunning && props.direction === 'rtl') {
      const rtlNewValue = value === 0 ? 2 : value - 1;
      setValue(rtlNewValue);
      props.setRotatingValue(props.index, rtlNewValue);
    }
  }, [value, props]);

可以通过将所需的props移至参数来进一步简化该操作:

  const counterIntervalFunction = React.useCallback((isRunning, direction, setRotatingValue, index) => {
    if (isRunning === false) {
      return;
    }

    if (direction === 'ltr') {
      const ltrNewValue = value === 2 ? 0 : value + 1;
      setValue(ltrNewValue);
      setRotatingValue(index, ltrNewValue);
    } else if (props.direction === 'rtl') {
      const rtlNewValue = value === 0 ? 2 : value - 1;
      setValue(rtlNewValue);
      setRotatingValue(index, rtlNewValue);
    }
  }, [value]);

如果不是setRotatingValue,这可能甚至更简单:现在,您有一个组件既可以保持自己的状态 则可以通知父对象状态更改。您应该意识到,组件状态value在调用时不一定会更新,但是setRotatingValue绝对会更新。这可能导致 parent 看到的状态不同于孩子的状态。我建议您更改数据流的方式,以使父级拥有当前值,并通过道具传递该值,而不是子级。

这使我们完成以下代码:

function Row = (props) => {
  const renderInterval = React.useRef();
  const [value, setValue] = React.useState(0);

  useEffect(() => {
    renderInterval.current = setInterval(counterIntervalFunction, props.isRunning, props.direction, props.setRotatingValue, props.index);
    return () => {
      cancelInterval(renderInterval.current);
    };
  }, [props, counterIntervalFunction]);

  const counterIntervalFunction = React.useCallback((isRunning, direction, setRotatingValue, index) => {
    if (isRunning === false) {
      return;
    }

    if (direction === 'ltr') {
      const ltrNewValue = value === 2 ? 0 : value + 1;
      setValue(ltrNewValue);
      setRotatingValue(index, ltrNewValue);
    } else if (props.direction === 'rtl') {
      const rtlNewValue = value === 0 ? 2 : value - 1;
      setValue(rtlNewValue);
      setRotatingValue(index, rtlNewValue);
    }
  }, [value]);


  ...
}

在此代码中,您会注意到,每次道具或功能更改时,我们都会运行效果。不幸的是,这意味着效果将在每个循环中返回,因为我们需要保留对value的新引用。除非您重构counterIntervalFunction而不用setRotatingValue 通知父对象,否则此组件将始终存在此问题,以使该函数不包含其自身的状态。解决此问题的另一种方法是使用setValue的函数形式:

  const counterIntervalFunction = React.useCallback((isRunning, direction, setRotatingValue, index) => {
    if (isRunning === false) {
      return;
    }

    setValue(value => {
      if (direction === 'ltr') {
        return value === 2 ? 0 : value + 1;
      } else if (direction ==' rtl') {
        return value === 0 ? 2 : value - 1;
      }
    })
  }, []);

由于不能保证状态更新是同步运行的,因此无法从setValue调用中提取值,然后再调用setRotatingValue函数。 :(您可以在setRotatingValue回调内潜在地调用setValue,但这给了我希比吉卜赛人。

答案 1 :(得分:1)

这是一个时间间隔,当您直接依靠名称为rowState的旧状态直接调用setState时,可能会使事情变得混乱,请尝试以下操作:

   setRowState(oldstate=> { ...rowState, value: oldstate.value+1 });