在setInterval中使用React状态挂钩时,状态不会更新

问题描述 投票:31回答:4

我正在尝试新的React Hooks并且有一个带有计数器的Clock组件,该计数器应该每秒增加一次。但是,该值不会超过一个。

function Clock() {
  const [time, setTime] = React.useState(0);
  React.useEffect(() => {
    const timer = window.setInterval(() => {
      setTime(time + 1);
    }, 1000);
    return () => {
      window.clearInterval(timer);
    };
  }, []);

  return (
    <div>Seconds: {time}</div>
  );
}

ReactDOM.render(<Clock />, document.querySelector('#app'));
<script src="https://unpkg.com/[email protected]/umd/react.development.js"></script>
<script src="https://unpkg.com/[email protected]/umd/react-dom.development.js"></script>

<div id="app"></div>
javascript reactjs react-hooks
4个回答
60
投票

原因是因为传递到setInterval闭包的回调只访问第一个渲染中的time变量,它在后续渲染中无法访问新的time值,因为第二次没有调用useEffect()

timesetInterval回调中的值始终为0。

就像你熟悉的setState一样,状态钩子有两种形式:一种是它处于更新状态,另一种是传递当前状态的回调形式。你应该使用第二种形式并读取setState中的最新状态值回调以确保在递增之前具有最新的状态值。

奖金:替代方法

丹·阿布拉莫夫(Dan Abramov)深入探讨了在他的setInterval中使用blog post和钩子的主题,并提供了解决这个问题的替代方法。强烈推荐阅读!

function Clock() {
  const [time, setTime] = React.useState(0);
  React.useEffect(() => {
    const timer = window.setInterval(() => {
      setTime(prevTime => prevTime + 1); // <-- Change this line!
    }, 1000);
    return () => {
      window.clearInterval(timer);
    };
  }, []);

  return (
    <div>Seconds: {time}</div>
  );
}

ReactDOM.render(<Clock />, document.querySelector('#app'));
<script src="https://unpkg.com/[email protected]/umd/react.development.js"></script>
<script src="https://unpkg.com/[email protected]/umd/react-dom.development.js"></script>

<div id="app"></div>

7
投票

当提供空输入列表时,useEffect函数仅在组件安装时评估一次。

setInterval的替代方法是每次更新状态时使用setTimeout设置新的间隔:

  const [time, setTime] = React.useState(0);
  React.useEffect(() => {
    const timer = setTimeout(() => {
      setTime(time + 1);
    }, 1000);
    return () => {
      clearTimeout(timer);
    };
  }, [time]);

setTimeout的性能影响微不足道,通常可以忽略不计。除非该组件对新设置的超时导致不良影响的时间敏感,否则setIntervalsetTimeout方法都是可接受的。


2
投票

另一种解决方案是使用useReducer,因为它将始终通过当前状态。

function Clock() {
  const [time, dispatch] = React.useReducer((state = 0, action) => {
    if (action.type === 'add') return state + 1
    return state
  });
  React.useEffect(() => {
    const timer = window.setInterval(() => {
      dispatch({ type: 'add' });
    }, 1000);
    return () => {
      window.clearInterval(timer);
    };
  }, []);

  return (
    <div>Seconds: {time}</div>
  );
}

ReactDOM.render(<Clock />, document.querySelector('#app'));
<script src="https://unpkg.com/[email protected]/umd/react.development.js"></script>
<script src="https://unpkg.com/[email protected]/umd/react-dom.development.js"></script>

<div id="app"></div>

0
投票

当时间改变时,告诉React重新渲染.opt out

function Clock() {
  const [time, setTime] = React.useState(0);
  React.useEffect(() => {
    const timer = window.setInterval(() => {
      setTime(time + 1);
    }, 1000);
    return () => {
      window.clearInterval(timer);
    };
  }, [time]);

  return (
    <div>Seconds: {time}</div>
  );
}

ReactDOM.render(<Clock />, document.querySelector('#app'));
<script src="https://unpkg.com/[email protected]/umd/react.development.js"></script>
<script src="https://unpkg.com/[email protected]/umd/react-dom.development.js"></script>

<div id="app"></div>
© www.soinside.com 2019 - 2024. All rights reserved.