Обновлять компонент React каждую секунду

115

Я играл с React и получил следующий компонент времени, который просто отображается Date.now()на экране:

import React, { Component } from 'react';

class TimeComponent extends Component {
  constructor(props){
    super(props);
    this.state = { time: Date.now() };
  }
  render(){
    return(
      <div> { this.state.time } </div>
    );
  }
  componentDidMount() {
    console.log("TimeComponent Mounted...")
  }
}

export default TimeComponent;

Как лучше всего заставить этот компонент обновляться каждую секунду, чтобы перерисовать время с точки зрения React?

снежный убийца
источник

Ответы:

153

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

componentDidMount() {
  this.interval = setInterval(() => this.setState({ time: Date.now() }), 1000);
}
componentWillUnmount() {
  clearInterval(this.interval);
}
Вайски
источник
2
Если вам нужна библиотека, которая инкапсулирует такие вещи, я сделалreact-interval-rerender
Энди
Я добавил свой метод setInterval в свой конструктор, и он работал лучше.
Актейфан
89

Следующий код представляет собой измененный пример с веб-сайта React.js.

Исходный код доступен здесь: https://reactjs.org/#a-simple-component

class Timer extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      seconds: parseInt(props.startTimeInSeconds, 10) || 0
    };
  }

  tick() {
    this.setState(state => ({
      seconds: state.seconds + 1
    }));
  }

  componentDidMount() {
    this.interval = setInterval(() => this.tick(), 1000);
  }

  componentWillUnmount() {
    clearInterval(this.interval);
  }

  formatTime(secs) {
    let hours   = Math.floor(secs / 3600);
    let minutes = Math.floor(secs / 60) % 60;
    let seconds = secs % 60;
    return [hours, minutes, seconds]
        .map(v => ('' + v).padStart(2, '0'))
        .filter((v,i) => v !== '00' || i > 0)
        .join(':');
  }

  render() {
    return (
      <div>
        Timer: {this.formatTime(this.state.seconds)}
      </div>
    );
  }
}

ReactDOM.render(
  <Timer startTimeInSeconds="300" />,
  document.getElementById('timer-example')
);
Мистер Поливирл
источник
1
Я почему-то получаю this.updater.enqueueSetState не является ошибкой функции при использовании этого подхода. Обратный вызов setInterval правильно привязан к компоненту this.
baldrs 03
16
Для таких идиотов, как я: не updater
называйте
3
В ES6 мне нужно изменить одну строку, например this.interval = setInterval (this.tick.bind (this), 1000);
Капил
Можете ли вы добавить ссылку на URL-адрес, на который ссылается? Спасибо ~
frederj
Я добавил метод форматирования и свойство «конструктор» для большей надежности.
Мистер Поливирл,
15

@Waisky предложил:

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

Если вы хотите сделать то же самое, используя хуки:

const [time, setTime] = useState(Date.now());

useEffect(() => {
  const interval = setInterval(() => setTime(Date.now()), 1000);
  return () => {
    clearInterval(interval);
  };
}, []);

По поводу комментариев:

Внутрь ничего пропускать не нужно []. Если вы передадите timeскобки, это означает, что эффект запускается каждый раз при timeизменении значения , т. Е. Он вызывает новый setIntervalкаждый раз, timechanges, что не является тем, что мы ищем. Мы хотим , чтобы вызывать только setIntervalодин раз , когда компонент смонтирован , а затем setIntervalзвонят setTime(Date.now())каждые 1000 секунд. Наконец, мы вызываем, clearIntervalкогда компонент размонтирован.

Обратите внимание, что компонент обновляется в зависимости от того, как вы его использовали time, каждый раз, когда timeизменяется значение . Это не имеет ничего общего с вводом timeв []из useEffect.

1 человек
источник
1
Почему вы передали пустой array ( []) в качестве второго аргумента useEffect?
Mekeor Melire
В документации React по перехватчику эффекта сказано: «Если вы хотите запустить эффект и очистить его только один раз (при монтировании и размонтировании), вы можете передать пустой массив ([]) в качестве второго аргумента». Но OP хочет, чтобы эффект запускался каждую секунду, то есть повторялся, то есть не только один раз.
Mekeor Melire
Я заметил разницу, если вы передадите [время] в массив, он будет создавать и уничтожать компонент каждый интервал. Если вы передадите пустой массив [], он будет продолжать обновлять компонент и отключать его только тогда, когда вы покидаете страницу. Но в любом случае, чтобы заставить его работать, мне пришлось добавить key = {time} или key = {Date.now ()}, чтобы заставить его на самом деле перерисовать.
Teebu,
8

В componentDidMountметоде жизненного цикла компонента вы можете установить интервал для вызова функции, которая обновляет состояние.

 componentDidMount() {
      setInterval(() => this.setState({ time: Date.now()}), 1000)
 }
erik-sn
источник
4
Верно, но, как следует из верхнего ответа, не забудьте очистить время, чтобы избежать утечки памяти: componentWillUnmount () {clearInterval (this.interval); }
Александр Фальк
4
class ShowDateTime extends React.Component {
   constructor() {
      super();
      this.state = {
        curTime : null
      }
    }
    componentDidMount() {
      setInterval( () => {
        this.setState({
          curTime : new Date().toLocaleString()
        })
      },1000)
    }
   render() {
        return(
          <div>
            <h2>{this.state.curTime}</h2>
          </div>
        );
      }
    }
Джагадиш
источник
0

Итак, вы были на правильном пути. Внутри себя componentDidMount()вы могли бы завершить работу, выполнив setInterval()запуск изменения, но помните, что способ обновления состояния компонентов - через setState(), поэтому внутри componentDidMount()вы могли бы сделать это:

componentDidMount() {
  setInterval(() => {
   this.setState({time: Date.now()})    
  }, 1000)
}

Кроме того, вы используете то, Date.now()что работает, с componentDidMount()реализацией, которую я предложил выше, но вы получите длинный набор обновлений неприятных чисел, которые не читаются человеком, но технически это время, обновляющееся каждую секунду в миллисекундах с 1 января 1970 года, но мы хочу , чтобы на этот раз для чтения , как мы люди чтения времени, поэтому в дополнении к обучению и реализации setIntervalвы хотите узнать о new Date()и toLocaleTimeString()и вы бы реализовать это нравится так:

class TimeComponent extends Component {
  state = { time: new Date().toLocaleTimeString() };
}

componentDidMount() {
  setInterval(() => {
   this.setState({ time: new Date().toLocaleTimeString() })    
  }, 1000)
}

Обратите внимание, что я также удалил constructor()функцию, она вам не обязательно нужна, мой рефакторинг на 100% эквивалентен инициализации сайта с помощью constructor()функции.

Даниэль
источник
0

Из-за изменений в React V16, где componentWillReceiveProps () устарел, это методология, которую я использую для обновления компонента. Обратите внимание, что приведенный ниже пример находится в Typescript и использует статический метод getDerivedStateFromProps для получения начального состояния и обновленного состояния при каждом обновлении Props.

    class SomeClass extends React.Component<Props, State> {
  static getDerivedStateFromProps(nextProps: Readonly<Props>): Partial<State> | null {
    return {
      time: nextProps.time
    };
  }

  timerInterval: any;

  componentDidMount() {
    this.timerInterval = setInterval(this.tick.bind(this), 1000);
  }

  tick() {
    this.setState({ time: this.props.time });
  }

  componentWillUnmount() {
    clearInterval(this.timerInterval);
  }

  render() {
    return <div>{this.state.time}</div>;
  }
}
jarora
источник