setInterval w aplikacji React

103

Wciąż jestem całkiem nowy w React, ale powoli się ścigałem i napotkałem coś, na czym utknąłem.

Próbuję zbudować komponent „timera” w Reakcie i szczerze mówiąc nie wiem, czy robię to dobrze (czy wydajnie). W moim kodu poniżej, ustawić stan powrócić obiektu { currentCount: 10 }i zostały bawiąc componentDidMount, componentWillUnmounti renderi mogę się tylko do stanu „odliczać” od 10 do 9.

Pytanie dwuczęściowe: Co się mylę? I czy istnieje bardziej efektywny sposób korzystania z setTimeout (zamiast używania componentDidMount& componentWillUnmount)?

Z góry dziękuję.

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;
Jose
źródło
2
bind(this)nie jest już potrzebne, reaguje teraz samodzielnie.
Derek Pollard
2
Twoja metoda timera nie aktualizuje currentCount
Bryan Chen
1
@Derek czy na pewno? Właśnie this.timer.bind(this)uruchomiłem mój, dodając jako ten. Samowyzwalacz nie zadziałał
robak
6
@Theworm @Derek jest zły, trochę. React.createClass (przestarzała) automatycznie przypisuje metody, ale class Clock extends Componentnie wiąże się automatycznie. Zależy to więc od tego, w jaki sposób tworzysz komponenty, czy potrzebujesz wiązania.
CallMeNorm

Odpowiedzi:

160

Widzę 4 problemy z Twoim kodem:

  • W metodzie timera zawsze ustawiasz aktualną liczbę na 10
  • Próbujesz zaktualizować stan w metodzie renderowania
  • Nie używasz setStatemetody, aby faktycznie zmienić stan
  • Nie przechowujesz swojego intervalId w stanie

Spróbujmy to naprawić:

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>
    );
}

Spowodowałoby to zmniejszenie licznika czasu z 10 do -N. Jeśli chcesz, aby licznik czasu spadał do 0, możesz użyć nieco zmodyfikowanej wersji:

timer: function() {
   var newCount = this.state.currentCount - 1;
   if(newCount >= 0) { 
       this.setState({ currentCount: newCount });
   } else {
       clearInterval(this.state.intervalId);
   }
},
dotnetom
źródło
Dziękuję Ci. To ma sens. Nadal jestem bardzo początkującym i próbuję zrozumieć, jak działa stan i co dzieje się w poszczególnych „kawałkach”, na przykład renderowanie.
Jose
Zastanawiam się jednak, czy konieczne jest użycie componentDidMount i componentWillUnmount, aby faktycznie ustawić interwał? EDYCJA: Właśnie zobaczyłem Twoją ostatnią zmianę. :)
Jose
@Jose Myślę, że componentDidMountto właściwe miejsce do wywoływania zdarzeń po stronie klienta, więc użyłbym go do zainicjowania odliczania. O jakiej innej metodzie inicjalizacji myślisz?
dotnetom
Nie miałem nic szczególnego na myśli, ale użycie tak wielu „kawałków” wewnątrz komponentu wydawało się nieporęczne. Przypuszczam, że to po prostu ja przyzwyczajam się do tego, jak działają bity w Reakcie. Jeszcze raz dziękuję!
Jose
4
Nie ma prawdziwej potrzeby przechowywania wartości setInterval jako części stanu, ponieważ nie ma to wpływu na renderowanie
Gil
32

Zaktualizowano 10-sekundowe odliczanie za pomocą 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;
Greg Herbowicz
źródło
20

Zaktualizowano 10-sekundowe odliczanie za pomocą hooków (nowa propozycja funkcji, która pozwala ci używać stanu i innych funkcji Reacta bez pisania klasy. Obecnie są one w React 16.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'));
Greg Herbowicz
źródło
Z React 16.8, React hooki są dostępne w stabilnej wersji.
Greg Herbowicz 05.04.19
4

Jeśli ktoś szuka podejścia React Hook do implementacji metody setInterval. Dan Abramov mówił o tym na swoim blogu . Sprawdź to, jeśli chcesz dobrze przeczytać na ten temat, w tym podejście klasowe. Zasadniczo kod jest niestandardowym hookiem, który zmienia funkcję setInterval jako deklaratywną.

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]);
}

Dla wygody zamieszczam również link CodeSandbox: https://codesandbox.io/s/105x531vkq

Jo E.
źródło
2

Dzięki @dotnetom, @ greg-herbowicz

Jeśli zwróci "this.state is undefined" - powiąż funkcję timera:

constructor(props){
    super(props);
    this.state = {currentCount: 10}
    this.timer = this.timer.bind(this)
}
tulsluper
źródło
0

Aktualizowanie stanu co sekundę w klasie reagowania. Zauważ, że mój index.js przekazuje funkcję, która zwraca bieżący czas.

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;
Ashok Shah
źródło