setInterval в приложении React

103

Я все еще новичок в React, но я медленно продирался и наткнулся на то, на чем застрял.

Я пытаюсь создать компонент «таймер» в React, и, честно говоря, я не знаю, правильно ли я делаю это (или эффективно). В моем коде ниже, я установить состояние , чтобы вернуть объект { currentCount: 10 }и играл с componentDidMount, componentWillUnmountи renderя могу получить только состояние на « обратный отсчет» от 10 до 9.

Вопрос из двух частей: что я ошибаюсь? И есть ли более эффективный способ использования setTimeout (вместо использования componentDidMount& componentWillUnmount)?

Заранее спасибо.

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)больше не нужен, react делает это самостоятельно.
Дерек Поллард
2
ваш метод таймера не обновляет currentCount
Брайан Чен,
1
@ Дерек, ты уверен? Я только что получил свою работу, добавив, this.timer.bind(this)поскольку this.timer сам по себе не работал
Червь
6
@Theworm @ Дерек вроде как ошибается. React.createClass (который устарел) автоматически связывает методы, но class Clock extends Componentне связывает автоматически. Таким образом, это зависит от того, как вы создаете свои компоненты, нужно ли вам связывать.
CallMeNorm

Ответы:

160

Я вижу 4 проблемы с вашим кодом:

  • В вашем методе таймера вы всегда устанавливаете текущий счет на 10
  • Вы пытаетесь обновить состояние в методе рендеринга
  • Вы не используете setStateметод для фактического изменения состояния
  • Вы не сохраняете свой intervalId в состоянии

Попробуем исправить это:

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

Это приведет к уменьшению таймера с 10 до -N. Если вам нужен таймер, который уменьшается до 0, вы можете использовать слегка измененную версию:

timer: function() {
   var newCount = this.state.currentCount - 1;
   if(newCount >= 0) { 
       this.setState({ currentCount: newCount });
   } else {
       clearInterval(this.state.intervalId);
   }
},
дотнетом
источник
Спасибо. В этом есть большой смысл. Я все еще новичок и пытаюсь понять, как работает состояние и что входит в какие «фрагменты», например, рендеринг.
Jose
Однако мне интересно, нужно ли использовать componentDidMount и componentWillUnmount для фактической установки интервала? РЕДАКТИРОВАТЬ: только что просмотрел ваше последнее изменение. :)
Jose
@Jose Я думаю, что componentDidMountэто подходящее место для запуска событий на стороне клиента, поэтому я бы использовал его для запуска обратного отсчета. О каком еще методе инициализации вы думаете?
dotnetom
Я не имел в виду ничего особенного, но использование такого количества «кусков» внутри компонента казалось неуклюжим. Я полагаю, что это просто я привыкаю к ​​тому, как отдельные элементы работают в React. Еще раз спасибо!
Jose
4
Нет никакой реальной необходимости хранить значение setInterval как часть состояния, потому что оно не влияет на рендеринг
Гил
32

Обновлен 10-секундный обратный отсчет с использованием 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

Обновлен 10-секундный обратный отсчет с использованием хуков (предложение новой функции, которое позволяет использовать состояние и другие функции React без написания класса. В настоящее время они находятся в 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'));
Грег Хербович
источник
В React 16.8 React Hooks доступны в стабильной версии.
Грег Хербович
4

Если кто-то ищет подход React Hook к реализации setInterval. Об этом Дан Абрамов рассказал в своем блоге . Проверьте это, если вы хотите хорошо прочитать эту тему, включая классовый подход. В основном код представляет собой настраиваемый хук, который превращает setInterval в декларативный.

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

Также разместите ссылку CodeSandbox для удобства: https://codesandbox.io/s/105x531vkq

Джо Э.
источник
2

Спасибо @dotnetom, @ greg-herbowicz

Если он возвращает "this.state is undefined" - привязать функцию таймера:

constructor(props){
    super(props);
    this.state = {currentCount: 10}
    this.timer = this.timer.bind(this)
}
Tulsluper
источник
0

Обновление состояния каждую секунду в классе реакции. Обратите внимание, что мой index.js передает функцию, возвращающую текущее время.

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;
Ашок Шах
источник