两个计时器中的一个没有正确执行React

vwkv1x7d  于 2022-10-15  发布在  React
关注(0)|答案(1)|浏览(126)

CountDown.js:

import React from 'react';
import ReactDOM from 'react-dom';

export default class CountDown extends React.Component {
    constructor(props) {
        super(props);
        this.state = {
            count: props.duration ? props.duration : 5, //?
        }
    }

    componentDidMount() {
      this.timer =  setInterval(() => {
            let {count} = this.state;
            this.setState({
                count: count - 1
            })
        }, 1000)
    }

    componentDidUpdate(prevState)
    {
        if(prevState.count !== this.state.count && this.state.count === 0)
        {
            clearInterval(this.timer);
            if (this.props.onTimesup) {
                this.props.onTimesup();
            }
        }
    }

    fmtMSS(s) { return (s - (s %= 60)) / 60 + (9 < s ? ':' : ':0') + s }
    render(){
        let { count } = this.state;
        console.log(this.fmtMSS(count));
        return 
    }
}

App.js:

const App = () => {
    const [canType, setCanType] = useState(0);
            let onTimesup = () => {
            setCanType(1);
            console.log("timer1 " + canType);
        }

        let onTimessup = () => {
            setCanType(0);
            console.log("timer2 " + canType);
        }
    }
     return (
    <div className="box d">
            <CountDown onTimesup={onTimesup} duration={2} />
          {canType == 1 &&
                <input type="text" onChange={onChange}></input>}
            </div>
            {canType == 1 &&
              <CountDown onTimesup={onTimessup} duration={5} /> }
    }

我想要的是设置一个两秒的计时器(Timer1),然后在它倒计时到0后设置“canType=1”,然后设置另一个计时器(Timer2)5秒,在倒计时到0之后设置“canType=0”并重复这个循环。不幸的是,系统是这样工作的:Console

olhwl3o2

olhwl3o21#

我可能会为这个用例做一个定制的钩子,跳过类组件和setInterval,这对于它本身的计时来说并不是非常准确,因为回调只能保证在回调毫秒值之后运行。使用日期比较和轮询间隔有助于缓解这种情况。
我在这里的方法仍然不是100%准确的,并且可能会在一个计时器到下一个计时器之间发生漂移(存储之前的到期时间以避免这种情况),但总体分辨率应该足够好。

const useTimer = ({delay, onTick, onExpiration}) => {
  React.useEffect(() => { // or useLayoutEffect
    if (!delay || !delay.ms) {
      return;
    }

    let ticks = 0;
    const start = new Date();
    const id = setInterval(() => {
      const now = new Date();
      const elapsed = now - start;

      if (elapsed / 1000 > ticks) {
        onTick(ticks++);
      }

      if (elapsed >= delay.ms) {
        onExpiration(ticks);
        clearInterval(id);
      }
    }, 1000 / 60);
    return () => clearInterval(id);
  }, [delay]);
};

const App = () => {
  const [value, setValue] = React.useState("");
  const [disabled, setDisabled] = React.useState(true);
  const [countDown, setCountDown] = React.useState(3);
  const [delays, setDelays] = React.useState([
    {ms: 2000}, {ms: 5000}
  ]);

  useTimer({
    delay: delays[0],
    onTick: () => setCountDown(c => c - 1),
    onExpiration: () => {
      if (delays.length > 1) {
        setCountDown(delays[1].ms / 1000 + 1);
      }

      setDisabled(p => !p);
      setDelays(d => d.slice(1));
    },
  });

  return (
    <div>
      <input
        onChange={({target: {value}}) => setValue(value)}
        value={value}
        disabled={disabled}
      ></input>
      <p>{countDown}</p>
    </div>
  );
};

ReactDOM.createRoot(document.querySelector("#app"))
  .render(<App />);
<script crossorigin src="https://unpkg.com/react@18/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom@18/umd/react-dom.development.js"></script>
<div id="app"></div>

如果您想更频繁地触发Tick回调,请将其除以100或10,而不是1000。您可以在每毫秒调用它,但对于向用户显示一个值来说,这似乎太过分了。他们可能最多只需要看到十分之一秒。

相关问题