Подтвердить что ты не робот

Создание секундомера с сокращением

Я пытаюсь сделать секундомер в реакции и сокращении. У меня возникли проблемы с выяснением того, как создать такую ​​вещь в редуксе.

Первое, что пришло в голову, - это действие START_TIMER, которое установило бы начальное значение offset. Сразу после этого я использую setInterval для повторного запуска действия TICK, которое вычисляет, сколько времени прошло с помощью смещения, добавляет его в текущее время и затем обновляет offset.

Этот подход, похоже, работает, но я не уверен, как я очищу интервал, чтобы остановить его. Кроме того, кажется, что этот дизайн плохой, и, вероятно, лучший способ это сделать.

Вот полный JSFiddle, который работает с START_TIMER. Если вы просто хотите посмотреть, как выглядит мой редуктор прямо сейчас, вот он:

const initialState = {
  isOn: false,
  time: 0
};

const timer = (state = initialState, action) => {
  switch (action.type) {
    case 'START_TIMER':
      return {
        ...state,
        isOn: true,
        offset: action.offset
      };

    case 'STOP_TIMER':
      return {
        ...state,
        isOn: false
      };

    case 'TICK':
      return {
        ...state,
        time: state.time + (action.time - state.offset),
        offset: action.time
      };

    default: 
      return state;
  }
}

Я бы очень признателен за любую помощь.

4b9b3361

Ответ 1

Я бы, вероятно, рекомендовал об этом по-другому: сохраните только состояние, необходимое для вычисления прошедшего времени в хранилище, и пусть компоненты устанавливают свой собственный интервал, хотя часто они хотят обновить дисплей.

Это приводит к тому, что действия отправляются на минимальные - только действия для запуска и остановки (и reset), которые отправляются таймером. Помните, что вы возвращаете новый объект состояния каждый раз, когда вы отправляете какое-либо действие, и каждый компонент connect ed затем повторно отображает (даже если они используют оптимизации, чтобы избежать слишком много повторных рендерингов внутри упакованных компонентов). Кроме того, многие сообщения о многих действиях могут затруднить отладку изменений состояния приложения, поскольку вам нужно иметь дело со всеми TICK наряду с другими действиями.

Вот пример:

// Action Creators

function startTimer(baseTime = 0) {
  return {
    type: "START_TIMER",
    baseTime: baseTime,
    now: new Date().getTime()
  };
}

function stopTimer() {
  return {
    type: "STOP_TIMER",
    now: new Date().getTime()
  };
}

function resetTimer() {
  return {
    type: "RESET_TIMER",
    now: new Date().getTime()
  }
}


// Reducer / Store

const initialState = {
  startedAt: undefined,
  stoppedAt: undefined,
  baseTime: undefined
};

function reducer(state = initialState, action) {
  switch (action.type) {
    case "RESET_TIMER":
      return {
        ...state,
        baseTime: 0,
        startedAt: state.startedAt ? action.now : undefined,
        stoppedAt: state.stoppedAt ? action.now : undefined
      };
    case "START_TIMER":
      return {
        ...state,
        baseTime: action.baseTime,
        startedAt: action.now,
        stoppedAt: undefined
      };
    case "STOP_TIMER":
      return {
        ...state,
        stoppedAt: action.now
      }
    default:
      return state;
  }
}

const store = createStore(reducer);

Обратите внимание, что создатели действия и редукторы имеют дело только с примитивными значениями и не используют какой-либо тип интервала или тип действия TICK. Теперь компонент может легко подписаться на эти данные и обновлять так часто, как он хочет:

// Helper function that takes store state
// and returns the current elapsed time
function getElapsedTime(baseTime, startedAt, stoppedAt = new Date().getTime()) {
  if (!startedAt) {
    return 0;
  } else {
    return stoppedAt - startedAt + baseTime;
  }
}

class Timer extends React.Component {
  componentDidMount() {
    this.interval = setInterval(this.forceUpdate.bind(this), this.props.updateInterval || 33);
  }

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

  render() {
    const { baseTime, startedAt, stoppedAt } = this.props;
    const elapsed = getElapsedTime(baseTime, startedAt, stoppedAt);

    return (
      <div>
        <div>Time: {elapsed}</div>
        <div>
          <button onClick={() => this.props.startTimer(elapsed)}>Start</button>
          <button onClick={() => this.props.stopTimer()}>Stop</button>
          <button onClick={() => this.props.resetTimer()}>Reset</button>
        </div>
      </div>
    );
  }
}

function mapStateToProps(state) {
  const { baseTime, startedAt, stoppedAt } = state;
  return { baseTime, startedAt, stoppedAt };
}

Timer = ReactRedux.connect(mapStateToProps, { startTimer, stopTimer, resetTimer })(Timer);

Вы даже можете отображать несколько таймеров на одних и тех же данных с другой частотой обновления:

class Application extends React.Component {
  render() {
    return (
      <div>
        <Timer updateInterval={33} />
        <Timer updateInterval={1000} />
      </div>
    );
  }
}

Вы можете увидеть рабочий JSBin с этой реализацией здесь: https://jsbin.com/dupeji/12/edit?js,output

Ответ 2

Если вы собираетесь использовать это в более крупном приложении, я бы использовал requestAnimationFrame вместо setInterval для проблем с производительностью. Поскольку вы показываете миллисекунды, вы заметили это на мобильных устройствах не столько на настольных браузерах.

Обновлен JSFiddle

https://jsfiddle.net/andykenward/9y1jjsuz

Ответ 3

Вы хотите использовать функцию clearInterval, которая берет результат вызова setInterval (уникальный идентификатор) и останавливает это интервал от выполнения каких-либо дальнейших действий.

Поэтому вместо объявления setInterval внутри start() вместо этого передайте его в редуктор, чтобы он мог хранить свой идентификатор в состоянии:

передать interval диспетчеру как член объекта действия

start() {
  const interval = setInterval(() => {
    store.dispatch({
      type: 'TICK',
      time: Date.now()
    });
  });

  store.dispatch({
    type: 'START_TIMER',
    offset: Date.now(),
    interval
  });
}

Сохранить interval в новом состоянии в редукторе действий START_TIMER

case 'START_TIMER':
  return {
    ...state,
    isOn: true,
    offset: action.offset,
    interval: action.interval
  };

______

Отображение компонента в соответствии с interval

Перейдите в interval как свойство компонента:

const render = () => {
  ReactDOM.render(
    <Timer 
      time={store.getState().time}
      isOn={store.getState().isOn}
      interval={store.getState().interval}
    />,
    document.getElementById('app')
  );
}

Затем мы можем проверить состояние внутри компонента, чтобы отобразить его в соответствии с тем, существует ли свойство interval или нет:

render() {
  return (
    <div>
      <h1>Time: {this.format(this.props.time)}</h1>
      <button onClick={this.props.interval ? this.stop : this.start}>
        { this.props.interval ? 'Stop' : 'Start' }
      </button>
    </div>
  );
}

______

Остановка таймера

Чтобы остановить таймер, мы очищаем интервал с помощью clearInterval и просто снова применяем initialState:

case 'STOP_TIMER':
  clearInterval(state.interval);
  return {
    ...initialState
  };

______

Обновлен JSFiddle

https://jsfiddle.net/8z16xwd2/2/