Таймер обратного отсчета в React

Я видел много таймеров обратного отсчета в JavaScript и хотел, чтобы один работал в React.

я позаимствовал эту функцию я нашел в интернете:

secondsToTime(secs){
    let hours = Math.floor(secs / (60 * 60));

    let divisor_for_minutes = secs % (60 * 60);
    let minutes = Math.floor(divisor_for_minutes / 60);

    let divisor_for_seconds = divisor_for_minutes % 60;
    let seconds = Math.ceil(divisor_for_seconds);

    let obj = {
        "h": hours,
        "m": minutes,
        "s": seconds
    };
    return obj;
  };

и тогда я сам написал этот код

  initiateTimer = () => {
    let timeLeftVar = this.secondsToTime(60);
    this.setState({ timeLeft: timeLeftVar })
  };

  startTimer = () => {
    let interval = setInterval(this.timer, 1000);
    this.setState({ interval: interval });
  };

  timer = () => {
    if (this.state.timeLeft >0){
      this.setState({ timeLeft: this.state.timeLeft -1 });
    }
    else {
      clearInterval(this.state.interval);
      //this.postToSlack();
    }
  };

В настоящее время onclick он установит время на экране:Time Remaining: 1 m : 0 s Но это не сводит его к Time Remaining: 0 m : 59 s а то Time Remaining: 0 m : 58 s etc etc

Я думаю, что мне нужно снова вызвать функцию с другим параметром. как я могу заниматься этим? это ?

EDIT: Я ЗАБЫЛ СКАЗАТЬ, Я ХОТЕЛ БЫ ФУНКЦИОНАЛЬНОСТЬ, ЧТОБЫ Я МОГ ИСПОЛЬЗОВАТЬ СЕКУНДЫ ДО МИНУТ И СЕКУНД

3 ответов


вы должны setState каждую секунду с оставшимися секундами (каждый раз, когда интервал вызывается). Вот пример:

class Example extends React.Component {
  constructor() {
    super();
    this.state = { time: {}, seconds: 5 };
    this.timer = 0;
    this.startTimer = this.startTimer.bind(this);
    this.countDown = this.countDown.bind(this);
  }

  secondsToTime(secs){
    let hours = Math.floor(secs / (60 * 60));

    let divisor_for_minutes = secs % (60 * 60);
    let minutes = Math.floor(divisor_for_minutes / 60);

    let divisor_for_seconds = divisor_for_minutes % 60;
    let seconds = Math.ceil(divisor_for_seconds);

    let obj = {
      "h": hours,
      "m": minutes,
      "s": seconds
    };
    return obj;
  }

  componentDidMount() {
    let timeLeftVar = this.secondsToTime(this.state.seconds);
    this.setState({ time: timeLeftVar });
  }

  startTimer() {
    if (this.timer == 0 && this.state.seconds > 0) {
      this.timer = setInterval(this.countDown, 1000);
    }
  }

  countDown() {
    // Remove one second, set state so a re-render happens.
    let seconds = this.state.seconds - 1;
    this.setState({
      time: this.secondsToTime(seconds),
      seconds: seconds,
    });
    
    // Check if we're at zero.
    if (seconds == 0) { 
      clearInterval(this.timer);
    }
  }

  render() {
    return(
      <div>
        <button onClick={this.startTimer}>Start</button>
        m: {this.state.time.m} s: {this.state.time.s}
      </div>
    );
  }
}

ReactDOM.render(<Example/>, document.getElementById('View'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id="View"></div>

проблема заключается в вашем значении" this". Функция таймера не может получить доступ к" state " prop, потому что выполняется в другом контексте. Я предлагаю вам сделать что-то вроде этого:

...
startTimer = () => {
  let interval = setInterval(this.timer.bind(this), 1000);
  this.setState({ interval });
};

Как вы можете видеть, я добавил метод "привязки" к вашей функции таймера. Это позволяет таймеру при вызове получить доступ к тому же" этому " вашего компонента react (это основная проблема/улучшение при работе с javascript в целом).

другой вариант-использовать другую стрелку функция:

startTimer = () => {
  let interval = setInterval(() => this.timer(), 1000);
  this.setState({ interval });
};

один недостаток с setInterval это то, что он может замедлить основной поток. Вы можете сделать таймер обратного отсчета с помощью requestAnimationFrame вместо этого, чтобы предотвратить это. Например, это мой общий компонент таймера обратного отсчета:

class Timer extends Component {
  constructor(props) {
    super(props)
    // here, getTimeRemaining is a helper function that returns an 
    // object with { total, seconds, minutes, hours, days }
    this.state = { timeLeft: getTimeRemaining(props.expiresAt) }
  }

  // Wait until the component has mounted to start the animation frame
  componentDidMount() {
    this.start()
  }

  // Clean up by cancelling any animation frame previously scheduled
  componentWillUnmount() {
    this.stop()
  }

  start = () => {
    this.frameId = requestAnimationFrame(this.tick)
  }

  tick = () => {
    const timeLeft = getTimeRemaining(this.props.expiresAt)
    if (timeLeft.total <= 0) {
      this.stop()
      // ...any other actions to do on expiration
    } else {
      this.setState(
        { timeLeft },
        () => this.frameId = requestAnimationFrame(this.tick)
      )
    }
  }

  stop = () => {
    cancelAnimationFrame(this.frameId)
  }

  render() {...}
}