這是一個因爲倒計時插件出現bug而出現的文章,致使我努力去尋找這個緣由的源頭,最後終於發現了新大陸(先事先展現一下新大陸的結論):react
首先,你得先了解js的Event Loop機制裏面的微任務和宏任務,否則,咱們的溝通缺乏了一個平臺。segmentfault
而後,就是問題的關鍵點了,爲何會出現這樣的狀況呢?
答案:以1秒爲例,setInterval和setTimeout都會在異步模塊運行,可是setInterval會每次都剛恰好1s鐘的時候,將微任務隊列的函數,交於任務隊列進行執行。若是setInterval第一次1.004s的時候將任務推動隊列,那麼這時候setInterval的每次的偏差會是在0.996s-1.01s內波動,而setTimeout每次的偏差值都會大於1s。這裏我畫個圖,相信你們就可以明白setInterval實現倒計時的很差之處(圖中進制以省略小數點爲準,四捨五入同理)異步
// 倒計時組件 import React, { useState, useEffect } from 'react'; interface CountdownInfo { hour: number; minute: number; second: number; day: number; } interface CountdownProps { sec: number; render: (data: CountdownInfo) => React.ReactNode; onEnd: () => void; } const Countdown: React.FC<CountdownProps> = ({ sec, render, onEnd }) => { const [endTime, setEndTime] = useState(new Date(Date.now() + sec*1000)); const [countdownInfo, setCountdownInfo] = useState({ hour: 0, minute: 0, second: 0, day: 0, } as CountdownInfo); const tick = () => { const seconds = Math.floor((endTime.getTime() - Date.now())/1000); const day = Math.floor(seconds / 86400); const hour = Math.floor((seconds % 86400) / 3600); const minute = Math.floor((seconds % 3600) / 60); const sec = seconds % 60; setCountdownInfo({ day, hour, minute, second: sec, } as CountdownInfo); if(seconds <= 0) { onEnd && onEnd(); } else { window.setTimeout(tick, 1000); // 時間不截至一直倒計時 } } useEffect(() => { setEndTime(new Date(Date.now() + sec*1000)); }, [sec]); useEffect(() => { tick(); // 初始化倒計時 }, []); return render(countdownInfo) || null; } export default Countdown;
引用方式函數
const renderCountdown = ({day, hour, minute, second}): React.ReactNode => { return ( <div> <span>{ day > 0 ? (day+'天') : null }</span> <span>hour</span>: <span>minute</span>: <span>second</span> </div> ); } const onEnd = () => { console.log("倒計時結束") } <Countdown sec={100} render={renderCountdown} onEnd={onEnd} />