如何制作React倒计时器

6
我想用 React 做一个倒计时器。它基本上是从 10 到 0 的倒计时,并且当计时到 0 时,我会调用一些函数。
我找到了一个非常适合我的例子:https://codesandbox.io/s/0q453m77nw?from-embed,但是它是一个类组件。我想使用函数式组件和 Hooks 实现,但是我做不到。
我尝试过:
function App() {
  const [seconds, setSeconds] = useState(10);
  useEffect(() => {
    setSeconds(setInterval(seconds, 1000));
  }, []);

  useEffect(() => {
    tick();
  });

  function tick() {
    if (seconds > 0) {
      setSeconds(seconds - 1)
    } else {
      clearInterval(seconds);
    }
  }

  return (

    <div className="App">
      <div
        {seconds}
      </div>
    </div>
  );
}

export default App;

它很快地倒计时从10到0,而不是在10秒钟内倒数。我错在哪里了?


为什么要使用钩子?这基本上和普通组件一样多的代码。 - Mike 'Pomax' Kamermans
1
@Mike'Pomax'Kamermans 尽管在当前设置中代码量大致相同,但有其他理由更喜欢函数组件而不是类组件。函数组件通常更易于阅读、测试,并且React团队声称在未来的React版本中可能会有性能激励。 - Brett DeWoody
4个回答

17

看起来是多个useEffect钩子造成了倒计时每秒运行一次以上的问题。

下面是一个简化的解决方案,我们在useEffect钩子中检查 seconds ,并执行以下两种操作之一:

  • 使用setTimeout在1秒后更新seconds
  • 执行其他操作(倒计时结束后要调用的函数)

这种方法有一些缺点,请参见下文。

function App() {
  const [seconds, setSeconds] = React.useState(10);

  React.useEffect(() => {
    if (seconds > 0) {
      setTimeout(() => setSeconds(seconds - 1), 1000);
    } else {
      setSeconds('BOOOOM!');
    }
  });

  return (
    <div className="App">
      <div>
        {seconds}
      </div>
    </div>
  );
}

ReactDOM.render(<App />, document.getElementById('root'))
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.12.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.11.0/umd/react-dom.production.min.js"></script>

<div id="root"></div>

缺点

使用setInterval的缺点是它可能会停止-例如,组件被卸载,您导航到不同的选项卡或关闭计算机。如果定时器需要更强的鲁棒性,则更好的替代方案是在状态中存储一个endTime(如全局存储或上下文),并让您的组件检查当前时间与endTime 以计算倒计时。


3

您关注准确性吗?如果是的话,您不想使用setInterval。如果您不关心准确性(您可能不关心),那么您可以按间隔时间安排对tick()的调用,而不是反过来。

const TimeoutComponent extends Component {
  constructor(props) {
    super(props);
    this.state = { countdown: 10 };
    this.timer = setInterval(() => this.tick(), props.timeout || 10000);
  }

  tick() {
    const current = this.state.countdown;
    if (current === 0) {
      this.transition();
    } else {
      this.setState({ countdown: current - 1 }); 
    } 
  }

  transition() {
    clearInterval(this.timer);
    // do something else here, presumably.
  }

  render() {
    return <div className="timer">{this.state.countDown}</div>;
  }
}

0
这取决于你的逻辑。在当前情况下,你运行 tick 方法的 useEffect 在每次渲染时都会运行。你可以在下面找到一个简单的例子。
function App() {
  const [seconds, setSeconds] = useState(10);
  const [done, setDone] = useState(false);
  const foo = useRef();

  useEffect(() => {
    function tick() {
        setSeconds(prevSeconds => prevSeconds - 1)
    }
    foo.current = setInterval(() => tick(), 1000)
  }, []);

  useEffect(() => {
    if (seconds  === 0) {
      clearInterval(foo.current);
      setDone(true);
    }
  }, [seconds])

  return (

    <div className="App">
    {seconds}
    {done && <p>Count down is done.</p>}
    </div>
  );
}

在第一个效果中,我们正在进行倒计时。使用回调函数来设置状态,因为间隔创建了一个闭包。在第二个效果中,我们正在检查我们的条件。

0

只需使用此代码片段,它还将帮助记忆超时回调。

const [timer, setTimer] = useState(60);    
const timeOutCallback = useCallback(() => setTimer(currTimer => currTimer - 1), []);

useEffect(() => {
  timer > 0 && setTimeout(timeOutCallback, 1000);
}, [timer, timeOutCallback]);

console.log(timer);

希望这能帮到你或其他人。
编程愉快!

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接