Aktualizuj komponent React co sekundę

114

Bawiłem się Reactem i mam następujący składnik czasu, który po prostu renderuje Date.now()się na ekranie:

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;

Jaki byłby najlepszy sposób, aby ten komponent aktualizował się co sekundę, aby ponownie narysować czas z perspektywy Reacta?

śnieżynka
źródło

Odpowiedzi:

152

Musisz użyć, setIntervalaby wywołać zmianę, ale musisz także wyczyścić licznik czasu, gdy komponent odmontuje się, aby zapobiec pozostawieniu błędów i wycieku pamięci:

componentDidMount() {
  this.interval = setInterval(() => this.setState({ time: Date.now() }), 1000);
}
componentWillUnmount() {
  clearInterval(this.interval);
}
Waiski
źródło
2
Jeśli chciałbyś mieć bibliotekę, która zawiera tego typu rzeczy, zrobiłemreact-interval-rerender
Andy
Dodałem moją metodę setInterval w moim konstruktorze i to działało lepiej.
aqteifan
89

Poniższy kod jest zmodyfikowanym przykładem ze strony React.js.

Oryginalny kod jest dostępny tutaj: 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')
);
Panie Polywhirl
źródło
1
Z jakiegoś powodu otrzymuję this.updater.enqueueSetState nie jest błędem funkcji podczas korzystania z tego podejścia. wywołanie zwrotne setInterval jest poprawnie powiązane ze składnikiem this.
baldrs
16
Dla idiotów takich jak ja: nie updater
nazywaj
3
W ES6 muszę zmienić jedną linię w ten sposób. Interval = setInterval (this.tick.bind (this), 1000);
Kapil
Czy możesz dodać link do adresu URL, do którego się odnosi? Dzięki ~
frederj
Dodałem metodę formatowania i właściwość „konstruktor” dla większej niezawodności.
Pan Polywhirl
15

@Waisky zasugerował:

Musisz użyć, setIntervalaby wywołać zmianę, ale musisz także wyczyścić licznik czasu, gdy komponent odmontuje się, aby zapobiec pozostawieniu błędów i wycieku pamięci:

Jeśli chcesz zrobić to samo, używając hooków:

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

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

Odnośnie komentarzy:

Nie musisz niczego podawać do środka []. Jeśli umieścisz timew nawiasach, oznacza to, że efekt jest uruchamiany za każdym razem, gdy wartość timezmian się zmienia, tj. Wywołuje on za setIntervalkażdym razem nowy , timezmiany, czego nie szukamy. Chcemy tylko powoływać setIntervalraz, gdy składnik zostanie zamontowana, a następnie setIntervalwywołuje setTime(Date.now())co 1000 sekund. Na koniec wywołujemy, clearIntervalgdy komponent jest odmontowany.

Zwróć uwagę, że komponent jest aktualizowany na podstawie tego, jak go użyłeś time, za każdym razem, gdy timezmienia się wartość . To nie ma nic wspólnego z wprowadzeniem timew []z useEffect.

1 osoba
źródło
1
Dlaczego przekazałeś pustą tablicę ( []) jako drugi argument useEffect?
Mekeor Melire
Dokumentacja Reacta na temat haka efektu mówi nam: „Jeśli chcesz uruchomić efekt i wyczyścić go tylko raz (podczas montowania i odmontowywania), możesz przekazać pustą tablicę ([]) jako drugi argument.” Ale OP chce, aby efekt występował co sekundę, tj. Powtarzał się, tj. Nie tylko raz.
Mekeor Melire
Różnica, którą zauważyłem, jeśli przekażesz [czas] w tablicy, utworzy i zniszczy komponent w każdym interwale. Jeśli przekażesz pustą tablicę [], będzie on odświeżał komponent i odmontował go dopiero po opuszczeniu strony. Ale w każdym przypadku, aby to zadziałało, musiałem dodać key = {time} lub key = {Date.now ()}, aby faktycznie zrezygnował.
Teebu
8

W componentDidMountmetodzie cyklu życia komponentu można ustawić interwał wywołania funkcji aktualizującej stan.

 componentDidMount() {
      setInterval(() => this.setState({ time: Date.now()}), 1000)
 }
erik-sn
źródło
4
Dobrze, ale jak sugeruje najlepsza odpowiedź, pamiętaj, aby wyczyścić czas, aby uniknąć wycieku pamięci: 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>
        );
      }
    }
Jagadeesh
źródło
0

Więc byłeś na dobrej drodze. Wewnątrz componentDidMount()mógłbyś zakończyć zadanie, implementując, setInterval()aby wywołać zmianę, ale pamiętaj, że sposób zaktualizowania stanu komponentów jest za pośrednictwem setState(), więc wewnątrz componentDidMount()mógłbyś to zrobić:

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

Używasz również tego, Date.now()co działa, z componentDidMount()implementacją, którą zaoferowałem powyżej, ale otrzymasz długi zestaw paskudnych aktualizacji liczb, które nie są czytelne dla człowieka, ale jest to technicznie czas aktualizowany co sekundę w milisekundach od 1 stycznia 1970 r., Ale my chcesz, aby ten czas był czytelny dla tego, jak my, ludzie, czytamy czas, więc oprócz uczenia się i wdrażania setIntervalchcesz się o nim dowiedzieć new Date()i toLocaleTimeString()wdrożyć go w następujący sposób:

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

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

Zauważ, że usunąłem również constructor()funkcję, niekoniecznie jej potrzebujesz, mój refaktor jest w 100% równoważny z zainicjowaniem strony z constructor()funkcją.

Daniel
źródło
0

Ze względu na zmiany w React V16, w których metoda componentWillReceiveProps () została wycofana, jest to metodologia, której używam do aktualizacji komponentu. Zauważ, że poniższy przykład jest w skrypcie Typescript i używa statycznej metody getDerivedStateFromProps, aby uzyskać stan początkowy i stan zaktualizowany za każdym razem, gdy Props są aktualizowane.

    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>;
  }
}
jarora
źródło