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.