如何用React Hooks实现带启停按钮的循环计数器?
Fixing Your React Counter with setInterval
Let's break down what's going wrong with your current code and build a working version that meets all your requirements: click Start to begin incrementing, click Stop to halt and reset to 1, and loop back to 1 automatically when the counter hits 10.
Issues in Your First Code
- You’re creating a new
setIntervalevery time the component re-renders (which happens on every state change) whenstartis true. This leads to multiple overlapping intervals running at once, making the counter speed up unexpectedly. - Using
varfor the interval variable is risky—it’s function-scoped and can lead to inconsistent references as the component re-renders. - Your state update syntax (like
setStopStatus((prev) => (prev = true))) is incorrect. You should return the new value directly, not assign it.
Issues in Your Second Code
- The empty dependency array in
useEffectruns the interval immediately on component mount, which is why it starts automatically without clicking Start. - Storing the interval ID as a global variable (
var handle) means it won’t reset properly on component re-renders or unmounts, leading to leftover intervals running in the background. - You’re still creating duplicate intervals when
startis true, which causes the counter to behave erratically.
Working Implementation
Here’s a clean, reliable version that fixes all these problems:
import React, { useState, useEffect, useRef } from "react"; function App() { const [count, setCount] = useState(1); const [isRunning, setIsRunning] = useState(false); // Use useRef to persist the interval ID across re-renders without triggering state changes const intervalRef = useRef(null); // Manage interval creation and cleanup with useEffect useEffect(() => { if (isRunning) { intervalRef.current = setInterval(() => { setCount(prev => { // Loop back to 1 when we reach the target value (10) return prev === 10 ? 1 : prev + 1; }); }, 1000); } // Cleanup function: clear interval when component unmounts or isRunning changes return () => { clearInterval(intervalRef.current); }; }, [isRunning]); const handleStart = () => { setIsRunning(true); }; const handleStop = () => { setIsRunning(false); setCount(1); // Reset counter to default value 1 }; return ( <div> <h1 style={{ padding: "5px", margin: "5px" }}>{count}</h1> <button style={{ padding: "5px", margin: "5px" }} onClick={handleStop}> Stop </button> <button style={{ padding: "5px", margin: "5px" }} onClick={handleStart}> Start </button> </div> ); } export default App;
Key Improvements
useReffor Interval ID: We useuseRefto store the interval ID so it persists across re-renders without causing unnecessary state updates.- Controlled
useEffect: The effect only runs whenisRunningchanges, so we create exactly one interval when starting and clear it immediately when stopping or unmounting. - Simplified State: Instead of separate
startandstopstates, we use a singleisRunningboolean to avoid conflicting state values. - Proper Loop Logic: The counter’s increment logic handles the loop back to 1 directly in the
setCountcallback, ensuring consistent behavior. - Memory Leak Prevention: The cleanup function in
useEffectguarantees we always clear the interval, even if the component unmounts unexpectedly.
How It Works
- Click Start: Sets
isRunningtotrue, triggering theuseEffectto start the interval. The counter increments every second, looping back to 1 when it hits 10. - Click Stop: Sets
isRunningtofalse, which triggers the cleanup function to clear the interval. It also resets the counter back to 1 immediately. - No overlapping intervals: We only create a new interval when
isRunningswitches fromfalsetotrue, and we always clear the old interval first.
内容的提问来源于stack exchange,提问作者blaster carter
相关产品推荐
相关产品推荐

