Aggiorna il componente React ogni secondo


114

Ho giocato con React e ho il seguente componente temporale che si limita Date.now()al rendering sullo schermo:

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;

Quale sarebbe il modo migliore per far sì che questo componente si aggiorni ogni secondo per ridisegnare l'ora da una prospettiva di React?

Risposte:


152

È necessario utilizzare setIntervalper attivare la modifica, ma è anche necessario azzerare il timer quando il componente si smonta per evitare che lasci errori e perdite di memoria:

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

2
Se desideri una libreria che racchiuda questo genere di cose, ho fattoreact-interval-rerender
Andy

Ho aggiunto il mio metodo setInterval nel mio costruttore e ha funzionato meglio.
aqteifan

89

Il codice seguente è un esempio modificato dal sito Web React.js.

Il codice originale è disponibile qui: 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
Per qualche motivo ho capito che this.updater.enqueueSetState non è un errore di funzione quando si utilizza questo approccio. Il callback setInterval è correttamente associato al componente this.
baldrs

16
Per gli idioti come me: non nominare il timer updaterperché rovina il ciclo di aggiornamento
baldrs

3
Con ES6 ho bisogno di cambiare una riga come this.interval = setInterval (this.tick.bind (this), 1000);
Kapil

Puoi aggiungere il link all'URL che fa riferimento? Grazie ~
frederj

Ho aggiunto un metodo di formattazione e una proprietà "costruttore" per una maggiore robustezza.
Mr. Polywhirl,

15

@Waisky ha suggerito:

È necessario utilizzare setIntervalper attivare la modifica, ma è anche necessario azzerare il timer quando il componente si smonta per evitare che lasci errori e perdite di memoria:

Se desideri fare la stessa cosa, usando gli Hooks:

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

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

Per quanto riguarda i commenti:

Non è necessario far passare nulla all'interno []. Se passi timetra parentesi, significa eseguire l'effetto ogni volta che timecambia il valore , cioè ne invoca uno nuovo setIntervalogni volta, timecambia, che non è quello che stiamo cercando. Vogliamo invocare solo setIntervaluna volta quando il componente viene montato e quindi setIntervalchiama setTime(Date.now())ogni 1000 secondi. Infine, invochiamoclearInterval quando il componente è smontato.

Tieni presente che il componente viene aggiornato, in base a come lo hai utilizzato time, ogni volta che il valore ditime modifiche. Questo non ha nulla a che fare con la messa timein []di useEffect.


1
Perché hai passato un array ( []) vuoto come secondo argomento a useEffect?
Mekeor Melire

La documentazione di React sull'hook degli effetti ci dice: "Se vuoi eseguire un effetto e ripulirlo solo una volta (su mount e unmount), puoi passare un array vuoto ([]) come secondo argomento." Ma OP vuole che l'effetto venga eseguito ogni secondo, cioè ripetuto, cioè non solo una volta.
Mekeor Melire

La differenza che ho notato se passi [time] nell'array, creerà e distruggerà il componente ogni intervallo. Se passi l'array vuoto [], continuerà ad aggiornare il componente e lo smonterà solo quando lasci la pagina. Ma, in entrambi i casi, per farlo funzionare, ho dovuto aggiungere key = {time} o key = {Date.now ()} per farlo rieseguire effettivamente.
Teebu

8

Nel componentDidMountmetodo del ciclo di vita del componente , è possibile impostare un intervallo per chiamare una funzione che aggiorna lo stato.

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

4
Corretto, ma come suggerisce la risposta principale, ricorda di cancellare il tempo per evitare perdite di memoria: componentWillUnmount () {clearInterval (this.interval); }
Alexander Falk

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

Quindi eri sulla strada giusta. All'interno del tuo componentDidMount()potresti aver terminato il lavoro implementando setInterval()per attivare la modifica, ma ricorda che il modo per aggiornare uno stato dei componenti è tramite setState(), quindi all'interno del tuo componentDidMount()potresti aver fatto questo:

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

Inoltre, usi Date.now()che funziona, con l' componentDidMount()implementazione che ho offerto sopra, ma otterrai una lunga serie di numeri sgradevoli che non sono leggibili dall'uomo, ma tecnicamente è il tempo che si aggiorna ogni secondo in millisecondi dal 1 gennaio 1970, ma noi vuoi rendere questo tempo leggibile per come noi umani leggiamo il tempo, quindi oltre a imparare e implementare setIntervalvuoi imparare new Date()e toLocaleTimeString()implementarlo in questo modo:

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

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

Si noti che ho anche rimosso la constructor()funzione, non ne hai necessariamente bisogno, il mio refactor è equivalente al 100% all'inizializzazione del sito con la constructor()funzione.


0

A causa dei cambiamenti in React V16 in cui componentWillReceiveProps () è stato deprecato, questa è la metodologia che utilizzo per aggiornare un componente. Si noti che l'esempio seguente è in Typescript e utilizza il metodo statico getDerivedStateFromProps per ottenere lo stato iniziale e lo stato aggiornato ogni volta che i Props vengono aggiornati.

    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>;
  }
}
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.