setInterval in un'app React


101

Sono ancora abbastanza nuovo in React, ma sto procedendo lentamente e ho riscontrato qualcosa su cui sono bloccato.

Sto cercando di costruire un componente "timer" in React e, ad essere onesto, non so se lo sto facendo bene (o in modo efficiente). Nel mio codice qui sotto, ho impostato lo stato per restituire un oggetto { currentCount: 10 }e hanno accarezzato componentDidMount, componentWillUnmounte rendere posso solo lo Stato a "conto alla rovescia" 10-9.

Domanda in due parti: cosa sto sbagliando? E c'è un modo più efficiente di usare setTimeout (piuttosto che usare componentDidMount& componentWillUnmount)?

Grazie in anticipo.

import React from 'react';

var Clock = React.createClass({

  getInitialState: function() {
    return { currentCount: 10 };
  },

  componentDidMount: function() {
    this.countdown = setInterval(this.timer, 1000);
  },

  componentWillUnmount: function() {
    clearInterval(this.countdown);
  },

  timer: function() {
    this.setState({ currentCount: 10 });
  },

  render: function() {
    var displayCount = this.state.currentCount--;
    return (
      <section>
        {displayCount}
      </section>
    );
  }

});

module.exports = Clock;

2
bind(this)non è più necessario, reagire lo fa da solo ora.
Derek Pollard

2
il metodo del timer non aggiorna currentCount
Bryan Chen

1
@Derek sei sicuro? Ho appena fatto funzionare il mio aggiungendo this.timer.bind(this)come this.timer da solo non funzionava
Il worm

6
@Theworm @Derek è sbagliato, più o meno. React.createClass (che è deprecato) autobind i metodi, ma class Clock extends Componentnon si autobind . Quindi dipende da come si creano i componenti se è necessario eseguire il binding.
CallMeNorm

Risposte:


157

Vedo 4 problemi con il tuo codice:

  • Nel metodo del timer imposti sempre il conteggio corrente a 10
  • Si tenta di aggiornare lo stato nel metodo di rendering
  • Non si utilizza il setStatemetodo per modificare effettivamente lo stato
  • Non stai memorizzando il tuo intervalId nello stato

Proviamo a risolverlo:

componentDidMount: function() {
   var intervalId = setInterval(this.timer, 1000);
   // store intervalId in the state so it can be accessed later:
   this.setState({intervalId: intervalId});
},

componentWillUnmount: function() {
   // use intervalId from the state to clear the interval
   clearInterval(this.state.intervalId);
},

timer: function() {
   // setState method is used to update the state
   this.setState({ currentCount: this.state.currentCount -1 });
},

render: function() {
    // You do not need to decrease the value here
    return (
      <section>
       {this.state.currentCount}
      </section>
    );
}

Ciò si tradurrebbe in un timer che diminuisce da 10 a -N. Se vuoi un timer che scenda a 0, puoi usare una versione leggermente modificata:

timer: function() {
   var newCount = this.state.currentCount - 1;
   if(newCount >= 0) { 
       this.setState({ currentCount: newCount });
   } else {
       clearInterval(this.state.intervalId);
   }
},

Grazie. Questo ha molto senso. Sono ancora un principiante e sto cercando di capire come funziona lo stato e cosa succede in quali "blocchi", come il rendering.
Jose

Mi chiedo, tuttavia, è necessario utilizzare componentDidMount e componentWillUnmount per impostare effettivamente l'intervallo? EDIT: ho appena visto la tua modifica più recente. :)
Jose

@ Jose penso che componentDidMountsia il posto giusto per attivare gli eventi lato client, quindi lo userei per avviare il conto alla rovescia. A quale altro metodo stai pensando per l'inizializzazione?
dotnetom

Non avevo in mente nient'altro in particolare, ma mi sembrava goffo usare così tanti "pezzi" all'interno di un componente. Suppongo che sia solo io ad abituarmi a come funzionano i pezzi in React. Grazie ancora!
Jose

4
Non è necessario memorizzare il valore setInterval come parte dello stato perché non influisce sul rendering
Gil

32

Conto alla rovescia aggiornato di 10 secondi utilizzando class Clock extends Component

import React, { Component } from 'react';

class Clock extends Component {
  constructor(props){
    super(props);
    this.state = {currentCount: 10}
  }
  timer() {
    this.setState({
      currentCount: this.state.currentCount - 1
    })
    if(this.state.currentCount < 1) { 
      clearInterval(this.intervalId);
    }
  }
  componentDidMount() {
    this.intervalId = setInterval(this.timer.bind(this), 1000);
  }
  componentWillUnmount(){
    clearInterval(this.intervalId);
  }
  render() {
    return(
      <div>{this.state.currentCount}</div>
    );
  }
}

module.exports = Clock;

20

Conto alla rovescia aggiornato di 10 secondi utilizzando Hooks (una nuova proposta di funzionalità che ti consente di utilizzare state e altre funzionalità di React senza scrivere una classe. Sono attualmente in React v16.7.0-alpha).

import React, { useState, useEffect } from 'react';
import ReactDOM from 'react-dom';

const Clock = () => {
    const [currentCount, setCount] = useState(10);
    const timer = () => setCount(currentCount - 1);

    useEffect(
        () => {
            if (currentCount <= 0) {
                return;
            }
            const id = setInterval(timer, 1000);
            return () => clearInterval(id);
        },
        [currentCount]
    );

    return <div>{currentCount}</div>;
};

const App = () => <Clock />;

ReactDOM.render(<App />, document.getElementById('root'));

Con React 16.8, React Hooks sono disponibili in una versione stabile.
Greg Herbowicz

2

Grazie @dotnetom, @ greg-herbowicz

Se restituisce "this.state is undefined" - bind timer function:

constructor(props){
    super(props);
    this.state = {currentCount: 10}
    this.timer = this.timer.bind(this)
}

2

Se qualcuno sta cercando un approccio React Hook per implementare setInterval. Dan Abramov ne ha parlato sul suo blog . Dai un'occhiata se vuoi una buona lettura sull'argomento, incluso un approccio di classe. Fondamentalmente il codice è un Hook personalizzato che trasforma setInterval come dichiarativo.

function useInterval(callback, delay) {
  const savedCallback = useRef();

  // Remember the latest callback.
  useEffect(() => {
    savedCallback.current = callback;
  }, [callback]);

  // Set up the interval.
  useEffect(() => {
    function tick() {
      savedCallback.current();
    }
    if (delay !== null) {
      let id = setInterval(tick, delay);
      return () => clearInterval(id);
    }
  }, [delay]);
}

Pubblicando anche il collegamento CodeSandbox per comodità: https://codesandbox.io/s/105x531vkq


0

Aggiornamento dello stato ogni secondo nella classe React. Nota che my index.js passa una funzione che restituisce l'ora corrente.

import React from "react";

class App extends React.Component {
  constructor(props){
    super(props)

    this.state = {
      time: this.props.time,

    }        
  }
  updateMe() {
    setInterval(()=>{this.setState({time:this.state.time})},1000)        
  }
  render(){
  return (
    <div className="container">
      <h1>{this.state.time()}</h1>
      <button onClick={() => this.updateMe()}>Get Time</button>
    </div>
  );
}
}
export default App;
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.