Как получить доступ к текущему состоянию перехватчика вне закрытия setInterval - PullRequest
0 голосов
/ 18 января 2020

первая публикация SO, так что любые советы будут полезны.

В настоящее время я преобразую компонент Timer с сохранением состояния для использования перехватчиков, но у меня возникают проблемы с доступом к текущему значению состояния в setInterval или setTimeout. Как компонент класса я легко мог получить доступ к современным значениям свойств состояния, однако из-за замыканий в setInterval / timeout я не могу получить доступ к современным значениям ловушек, когда они установлены вне setInterval / setTimeout (в паузе / воспроизведении onClick).

import React, { useState, useEffect, useRef } from 'react';

import { Link } from 'react-router-dom';

const Timer = (props) => {

  let [previousTime, setPreviousTime] = useState(new Date(props.entry.timestamp).getTime());
  let [elapsedTime, setElapsedTime] = useState(0);
  let isRunning = useRef(true);

  useEffect(() => {
    const timerInterval = setInterval(startTimer, 1000);
    return () => clearInterval(timerInterval);
  }, []);

  function startTimer() {
    console.log('previousTime', previousTime);
    console.log('elapsedTime', elapsedTime);
    if (isRunning.current) {
      const now = Date.now();
      setElapsedTime(elapsedTime + (now - previousTime));
      setPreviousTime(now);
    }
  }

  function handlePause() {
    isRunning.current = isRunning.current ? false : true
    console.log('isRunning', isRunning.current);
    if (!isRunning.current) {
      setPreviousTime(Date.now());
      console.log(previousTime);
    }
  }

  let hours = 0, minutes = 0, seconds = 0;
  let secondsDiffence, secondsText, minutesText, hoursText;
  secondsDiffence = Math.floor(elapsedTime / 1000);

  hours = Math.floor(secondsDiffence / 3600);
  minutes = Math.floor((secondsDiffence - (hours * 3600)) / 60);
  seconds = Math.floor(secondsDiffence - (hours * 3600) - (minutes * 60));

  secondsText = seconds < 10 ? `0${seconds}` : `${seconds}`;
  minutesText = minutes < 10 ? `0${minutes}` : `${minutes}`;
  hoursText = hours < 10 ? `0${hours}` : `${hours}`;

  console.log('elapsed outside -->', elapsedTime);

  return (
    <div>
      <div id="timer">
        {`${hoursText}:${minutesText}:${secondsText}`}
      </div>
      <button 
        id="pausePlay"
        className="mainButton"
        onClick={handlePause}>
          {isRunning.current ? 'Pause' : 'Continue'}
      </button>
      <Link to="/" id="stopSave" className="mainButton" onClick={() => {
        props.updateEntry(props.newEntry, elapsedTime, "elapsedTime");
        props.submitEntry();
      }}>Stop & Save</Link>
    </div>
  );
}

export default Timer;

Предыдущий компонент рабочего класса представлен ниже:

import React from 'react';

import { Link } from 'react-router-dom';


class Timer extends React.Component {
  constructor(props) {
    super(props);

    this.state = {
      isRunning: true,
      previousTime: new Date(this.props.entry.timestamp).getTime(),
      elapsedTime: 0
    };

    this.startTimer = this.startTimer.bind(this);
    this.handlePause = this.handlePause.bind(this);
  }

  componentDidMount() {
    this.timerInstance = setInterval(() => this.startTimer(), 1000);
  }

  componentWillUnmount() {
    clearInterval(this.timerInstance);
  }

  startTimer() {
    if (this.state.isRunning) {
      const now = Date.now();
      this.setState(prevState => ({
        previousTime: now,
        elapsedTime: prevState.elapsedTime + (now - prevState.previousTime)
      }));
    }
  }

  handlePause() {
    this.setState(prevState => ({
      isRunning: !prevState.isRunning
    }));
    if (!this.state.isRunning) {
      this.setState({
        previousTime: Date.now()
      });
    }
  }

  render() {
    let hours = 0, minutes = 0, seconds = 0;
    let secondsDiffence, secondsText, minutesText, hoursText;
    secondsDiffence = Math.floor(this.state.elapsedTime / 1000);

    hours = Math.floor(secondsDiffence / 3600);
    minutes = Math.floor((secondsDiffence - (hours * 3600)) / 60);
    seconds = Math.floor(secondsDiffence - (hours * 3600) - (minutes * 60));

    secondsText = seconds < 10 ? `0${seconds}` : `${seconds}`;
    minutesText = minutes < 10 ? `0${minutes}` : `${minutes}`;
    hoursText = hours < 10 ? `0${hours}` : `${hours}`;

    return (
      <div>
        <div id="timer">{`${hoursText}:${minutesText}:${secondsText}`}</div>
        <button id="pausePlay" className="mainButton" onClick={this.handlePause}>{this.state.isRunning ? 'Pause' : 'Continue'}</button>
        <Link to="/" id="stopSave" className="mainButton" onClick={() => {
          props.updateEntry(props.newEntry, this.state.elapsedTime, "elapsedTime");
          props.submitEntry();
        }}>Stop & Save</Link>
      </div>
    );
  }
}

export default Timer;

1 Ответ

0 голосов
/ 18 января 2020

Ах, это проблема classi c при переходе от компонента Class к компоненту на основе хуков. Вам нужно будет выучить то, что вы знаете из компонента класса, чтобы понять, что произошло. (Чтобы узнать больше, я рекомендую вам прочитать эту статью, это действительно полезно: https://overreacted.io/a-complete-guide-to-useeffect/)

Так что в компоненте на основе хуков каждый раз, когда происходит рендеринг, React будет вызовите функцию компонента, чтобы узнать дерево DOM. Таким образом, в основном, каждый раз, когда вызывается функция, существует отдельный стек вызовов, и каждое состояние внутри функции будет иметь различное значение, и, следовательно, обратный вызов для setInterval / setTimer будет иметь закрывающий доступ к значению, когда он создается (каждый раз, когда вызывается функция, будет отдельный обратный вызов startTimer).

Вот где в игру вступает хук useEffect. Вам нужно будет указать хуку useEffect «обновить» обратный вызов, чтобы получить доступ к последней «версии» состояний. Проще говоря, вот мое предложение:

import React, { useState, useEffect, useRef } from 'react';

import { Link } from 'react-router-dom';

const Timer = (props) => {

  let [previousTime, setPreviousTime] = useState(new Date(props.entry.timestamp).getTime());
  let [elapsedTime, setElapsedTime] = useState(0);
  let isRunning = useRef(true);

  useEffect(() => {
    function startTimer() {
      console.log('previousTime', previousTime);
      console.log('elapsedTime', elapsedTime);
      if (isRunning.current) {
        const now = Date.now();
        setElapsedTime(elapsedTime + (now - previousTime));
        setPreviousTime(now);
      }
     }
    const timerInterval = setInterval(startTimer, 1000);
    return () => clearInterval(timerInterval);
  }, [previousTime, elapsedTime]); // The callback depends on those state, you need to put them here

  function handlePause() {
    isRunning.current = isRunning.current ? false : true
    console.log('isRunning', isRunning.current);
    if (!isRunning.current) {
      setPreviousTime(Date.now());
      console.log(previousTime);
    }
  }

  let hours = 0, minutes = 0, seconds = 0;
  let secondsDiffence, secondsText, minutesText, hoursText;
  secondsDiffence = Math.floor(elapsedTime / 1000);

  hours = Math.floor(secondsDiffence / 3600);
  minutes = Math.floor((secondsDiffence - (hours * 3600)) / 60);
  seconds = Math.floor(secondsDiffence - (hours * 3600) - (minutes * 60));

  secondsText = seconds < 10 ? `0${seconds}` : `${seconds}`;
  minutesText = minutes < 10 ? `0${minutes}` : `${minutes}`;
  hoursText = hours < 10 ? `0${hours}` : `${hours}`;

  console.log('elapsed outside -->', elapsedTime);

  return (
    <div>
      <div id="timer">
        {`${hoursText}:${minutesText}:${secondsText}`}
      </div>
      <button 
        id="pausePlay"
        className="mainButton"
        onClick={handlePause}>
          {isRunning.current ? 'Pause' : 'Continue'}
      </button>
      <Link to="/" id="stopSave" className="mainButton" onClick={() => {
        props.updateEntry(props.newEntry, elapsedTime, "elapsedTime");
        props.submitEntry();
      }}>Stop & Save</Link>
    </div>
  );
}

export default Timer;

Вот еще одна ссылка, которую вы можете оформить:

https://overreacted.io/making-setinterval-declarative-with-react-hooks/

https://reactjs.org/docs/hooks-reference.html

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...