首页 > 解决方案 > 为什么我的计时器挂钩不更新它的内部状态?

问题描述

由于某种原因,在我修改输入字段后,我的计时器没有更新它的内部计时器状态。这是我的页面和状态的初始状态。

初始状态

这是我将输入从 10 秒修改为 8 秒后的屏幕和状态。请注意,计时器状态不会更新

定时器状态未更新

这是我的锻炼页面代码:

function WorkoutPage(props: any) {

const DEFAULT_SECONDS_BETWEEN_REPS: number = 10
const [secondsBetweenRepsSetting, setSecondsBetweenRepsSetting] = useState(DEFAULT_SECONDS_BETWEEN_REPS)
const {secondsLeft, isRunning, start, stop} = useTimer({
        duration: secondsBetweenRepsSetting,
        onExpire: () => sayRandomExerciseName(),
        onTick: () => handleTick(),
    })
const onTimeBetweenRepsChange = (event: any) => {
        const secondsBetweenRepsSettingString = event.target.value;
        const secondsBetweenRepsSettingInt = parseInt(secondsBetweenRepsSettingString)
        setSecondsBetweenRepsSetting(secondsBetweenRepsSettingInt)
    }

return <React.Fragment>
<input type="number" name="secondsBetweenRepsSetting" value={secondsBetweenRepsSetting} onChange={onTimeBetweenRepsChange}/>
</React.Fragment>

}

这是我的 useTimer 类:

import { useState } from 'react';
import Validate from "../utils/Validate";
import useInterval from "./useInterval";

export default function useTimer({ duration: timerDuration, onExpire, onTick}) {
  const [secondsLeft, setSecondsLeft] = useState(timerDuration)
  const [isRunning, setIsRunning] = useState(false)

  function start() {
    setIsRunning(true)
  }
  function stop() {
    setIsRunning(false)
  }

  function handleExpire() {
    Validate.onExpire(onExpire) && onExpire();
  }

  useInterval(() => {
    const secondsMinusOne = secondsLeft - 1;
    setSecondsLeft(secondsMinusOne)
    if(secondsMinusOne <= 0) {
      setSecondsLeft(timerDuration) // Reset timer automatically
      handleExpire()
    } else {
      Validate.onTick(onTick) && onTick();
    }
  }, isRunning ? 1000 : null)

  return {secondsLeft, isRunning, start, stop, }
}

如果有人感兴趣,我的完整代码库在这里:https ://github.com/kamilski81/bdt-coach

标签: react-hooks

解决方案


以下是您期望的事件顺序:

  • 用户更改输入
  • setSecondsBetweenRepsSetting更改处理程序触发并使用新值调用
  • 组件使用新值重新渲染secondsBetweenRepsSetting
  • useTimerduration使用新值的属性调用
  • 钩子中的secondsLeft状态useTimer更改为新duration<-- 哎呀!这不会发生

为什么最后一项没有发生?因为在useTimer实现中,您使用持续时间的唯一地方是作为 的初始secondsLeft。使用新的持续时间值再次调用钩子不会改变secondsLeft状态,这是设计使然。

我的建议是包含setSecondsLeftuseTimer钩子的返回值中,以便为您提供一种方法来覆盖计时器中剩余的时间。然后,您可以setSecondsLeft直接在输入更改处理程序中使用:

const { secondsLeft, setSecondsLeft, isRunning, start, stop } = useTimer({
  duration: secondsBetweenRepsSetting,
  onExpire: () => sayRandomExerciseName(),
  onTick: () => handleTick(),
});

const onTimeBetweenRepsChange = (event: any) => {
  const secondsBetweenRepsSettingString = event.target.value;
  const secondsBetweenRepsSettingInt = parseInt(
    secondsBetweenRepsSettingString
  );
  setSecondsBetweenRepsSetting(secondsBetweenRepsSettingInt);
  setSecondsLeft(secondsBetweenRepsSettingInt);
};

推荐阅读