Оновлюйте компонент React щосекунди


114

Я граю з 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;

Що було б найкращим способом змусити цей компонент оновлюватись щосекунди, щоб повторно намалювати час з точки зору реагування?

Відповіді:


152

Потрібно використовувати setIntervalдля запуску зміни, але також потрібно очистити таймер, коли компонент відключений, щоб запобігти появі помилок і витоку пам'яті:

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

2
Якщо ви хочете, щоб бібліотека, яка інкапсулює подібні речі, я створилаreact-interval-rerender
Енді,

Я додав у свій конструктор метод setInterval, і це працювало краще.
aqteifan

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 зворотний виклик належним чином прив'язаний до цього компонента.
лисини

16
Для таких ідіотів, як я: не updater
називайте

3
З ES6 мені потрібно змінити такий рядок, як this.interval = setInterval (this.tick.bind (this), 1000);
Капіль

Чи можете ви додати посилання на URL, на який посилається? Спасибі ~
frederj

Я додав метод форматування та властивість "конструктор" для більшої надійності.
Пан Polywhirl

15

@Waisky запропонував:

Потрібно використовувати setIntervalдля запуску зміни, але також потрібно очистити таймер, коли компонент відключений, щоб запобігти появі помилок і витоку пам'яті:

Якщо ви хочете зробити те саме, використовуючи Гачки:

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

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

Щодо коментарів:

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

Зауважте, що компонент оновлюється, виходячи з того, як ви його використовували time, щоразу timeзмінюючи значення . Це не має нічого спільного з введенням timeв []з useEffect.


1
Чому ви передали порожній масив ( []) як другий аргумент useEffect?
Mekeor Melire

Документація React про гачок ефектів говорить нам: "Якщо ви хочете запустити ефект і очистити його лише один раз (під час монтажу та відключення), ви можете передати порожній масив ([]) як другий аргумент." Але ОП хоче, щоб ефект запускався щосекунди, тобто повторювався, тобто не один раз.
Mekeor Melire

Різниця, яку я помітив, якщо ви переходите [час] у масив, він створюватиме та знищує компонент кожен інтервал. Якщо ви передасте порожній масив [], він буде постійно оновлювати компонент і відключати його лише тоді, коли ви залишаєте сторінку. Але в будь-якому випадку, щоб змусити його працювати, мені довелося додати key = {time} або key = {Date.now ()}, щоб реально відобразити його.
Teebu

8

У componentDidMountспособі життєвого циклу компонента можна встановити інтервал для виклику функції, яка оновлює стан.

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

4
Правильно, але як підказує головна відповідь, не забудьте очистити час, щоб уникнути витоку пам'яті: компонентWillUnmount () {clearInterval (this.interval); }
Олександр Фолк

3
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()функцію, вона вам не обов'язково потрібна, мій рефактор стовідсотковий еквівалент ініціалізації сайту з constructor()функцією.


0

Завдяки змінам у React V16, де компонент WillReceiveProps () застарілий, це методика, яку я використовую для оновлення компонента. Зауважте, що наведений нижче приклад знаходиться в Typescript і використовує статичний метод getDerivedStateFromProps для отримання початкового стану та оновленого стану щоразу, коли реквізити оновлюються.

    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>;
  }
}
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.