ตามที่คนอื่น ๆ ได้ชี้ให้เห็นปัญหาคือuseState
ถูกเรียกเพียงครั้งเดียว (เป็นdeps = []
) เพื่อตั้งค่าช่วงเวลา:
React.useEffect(() => {
const timer = window.setInterval(() => {
setTime(time + 1);
}, 1000);
return () => window.clearInterval(timer);
}, []);
จากนั้นทุกครั้งที่setInterval
ติ๊กก็จะโทรตามจริงsetTime(time + 1)
แต่time
จะเก็บค่าที่มีในตอนแรกเสมอเมื่อมีsetInterval
การกำหนดการเรียกกลับ (การปิด)
คุณสามารถใช้รูปแบบอื่นของตัวuseState
ตั้งค่าและระบุการเรียกกลับแทนที่จะเป็นค่าจริงที่คุณต้องการตั้งค่า (เช่นเดียวกับsetState
):
setTime(prevTime => prevTime + 1);
แต่ฉันขอแนะนำให้คุณสร้างuseInterval
hook ของคุณเองเพื่อให้คุณสามารถ DRY และทำให้โค้ดของคุณง่ายขึ้นโดยใช้อย่างsetInterval
เปิดเผยตามที่ Dan Abramov แนะนำที่นี่ในMaking setInterval Declarative with React Hooks :
function useInterval(callback, delay) {
const intervalRef = React.useRef();
const callbackRef = React.useRef(callback);
React.useEffect(() => {
callbackRef.current = callback;
}, [callback]);
React.useEffect(() => {
if (typeof delay === 'number') {
intervalRef.current = window.setInterval(() => callbackRef.current(), delay);
return () => window.clearInterval(intervalRef.current);
}
}, [delay]);
return intervalRef;
}
const Clock = () => {
const [time, setTime] = React.useState(0);
const [isPaused, setPaused] = React.useState(false);
const intervalRef = useInterval(() => {
if (time < 10) {
setTime(time + 1);
} else {
window.clearInterval(intervalRef.current);
}
}, isPaused ? null : 1000);
return (<React.Fragment>
<button onClick={ () => setPaused(prevIsPaused => !prevIsPaused) } disabled={ time === 10 }>
{ isPaused ? 'RESUME ⏳' : 'PAUSE 🚧' }
</button>
<p>{ time.toString().padStart(2, '0') }/10 sec.</p>
<p>setInterval { time === 10 ? 'stopped.' : 'running...' }</p>
</React.Fragment>);
}
ReactDOM.render(<Clock />, document.querySelector('#app'));
body,
button {
font-family: monospace;
}
body, p {
margin: 0;
}
p + p {
margin-top: 8px;
}
#app {
display: flex;
flex-direction: column;
align-items: center;
min-height: 100vh;
}
button {
margin: 32px 0;
padding: 8px;
border: 2px solid black;
background: transparent;
cursor: pointer;
border-radius: 2px;
}
<script src="https://unpkg.com/react@16.7.0-alpha.0/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom@16.7.0-alpha.0/umd/react-dom.development.js"></script>
<div id="app"></div>
นอกเหนือจากการสร้างโค้ดที่ง่ายและสะอาดขึ้นแล้วยังช่วยให้คุณสามารถหยุดชั่วคราว (และล้าง) ช่วงเวลาโดยอัตโนมัติเพียงแค่ส่งdelay = null
และส่งคืน ID ช่วงเวลาในกรณีที่คุณต้องการยกเลิกด้วยตนเองด้วยตนเอง (ซึ่งไม่ครอบคลุมในโพสต์ของ Dan)
อันที่จริงสิ่งนี้สามารถปรับปรุงได้เพื่อไม่ให้รีสตาร์ทdelay
เมื่อยกเลิกการหยุดชั่วคราว แต่ฉันเดาว่าสำหรับกรณีการใช้งานส่วนใหญ่นี่ดีพอ
หากคุณกำลังมองหาคำตอบที่คล้ายกันสำหรับsetTimeout
มากกว่าsetInterval
, ตรวจสอบนี้ออก: https://stackoverflow.com/a/59274757/3723993
นอกจากนี้คุณยังสามารถหารุ่นที่เปิดเผยของsetTimeout
และsetInterval
, useTimeout
และuseInterval
บวกที่กำหนดเองuseThrottledCallback
เบ็ดเขียนใน typescript ในhttps://gist.github.com/Danziger/336e75b6675223ad805a88c2dfdcfd4a