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;

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

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;

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

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


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

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;
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.