Состояние не обновляется при использовании обработчика состояния React в setInterval - PullRequest
0 голосов
/ 27 октября 2018

Я пробую новые 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/react@16.7.0-alpha.0/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom@16.7.0-alpha.0/umd/react-dom.development.js"></script>

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

Ответы [ 4 ]

0 голосов
/ 20 марта 2019

Скажите React сделать рендер при изменении времени. отказаться

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/react@16.7.0-alpha.0/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom@16.7.0-alpha.0/umd/react-dom.development.js"></script>

<div id="app"></div>
0 голосов
/ 28 января 2019

useEffect функция оценивается только один раз при монтировании компонента, когда предоставляется пустой список ввода.

Альтернативой setInterval является установка нового интервала с помощью setTimeout каждый раз при обновлении состояния:

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

Влияние на производительность setTimeout незначительно и обычно может быть проигнорировано.Если компонент не чувствителен ко времени до точки, в которой вновь установленные таймауты вызывают нежелательные эффекты, подходы setInterval и setTimeout являются приемлемыми.

0 голосов
/ 28 января 2019

Альтернативным решением будет использование 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/react@16.7.0-alpha.0/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom@16.7.0-alpha.0/umd/react-dom.development.js"></script>

<div id="app"></div>
0 голосов
/ 27 октября 2018

Причина в том, что обратный вызов, переданный в замыкание setInterval, обращается только к переменной time в первом рендере, он не имеет доступа к новому значению time в последующем рендере, потому что useEffect() не вызывается во второй раз.

time всегда имеет значение 0 в обратном вызове setInterval.

Как и setState, с которым вы знакомы, хуки состояния имеют две формы: одну, где она принимает обновленное состояние, и форму обратного вызова, в которую передается текущее состояние. Вам следует использовать вторую форму и прочитать последнюю значение состояния в обратном вызове setState, чтобы убедиться, что у вас есть самое последнее значение состояния перед его увеличением.

Бонус: альтернативные подходы

Дан Абрамов, подробно изучает тему использования setInterval с крючками в своем посте в блоге и предлагает альтернативные способы решения этой проблемы. Настоятельно рекомендуем прочитать!

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/react@16.7.0-alpha.0/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom@16.7.0-alpha.0/umd/react-dom.development.js"></script>

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