Estoy trabajando en un reloj Pomodoro. Para construir las cuentas regresivas, estoy usando useEffect y setTimeout. Todo parecía estar bien hasta que me di cuenta de que hay un retraso de 30ms a 50ms entre cada segundo del reloj. ¿Cómo lo configuraría para que actualice el reloj de manera costosa cada 1000 ms?
Estoy usando useState para manejar la hora del reloj y los controles para detener, pausar y restablecer. Todos están funcionando correctamente. Es solo el momento del segundo el que se está retrasando más de lo que debería.
function App() { const [workTarget, setWorkTarget] = useState(25 * 60); const [breakTarget, setBreakTarget] = useState(5 * 60); const [time, setTime] = useState(workTarget); //time in seconds const [counting, setCounting] = useState(false); const [working, setWorking] = useState(true); const [clockTarget, setClockTarget] = useState(workTarget); const [combo, setCombo] = useState(0); const [config, setConfig] = useState(false); const [playWork] = useSound(workSfx); const [playBreak] = useSound(breakSfx); const [playPause] = useSound(pauseSfx); let tick = 1000; let timeout; let timenow = Date.now(); // Handle pause and stop of countdown useEffect(() => { if (time > 0 && counting === true) { timeout = setTimeout(() => { setTime(time - 1); console.log(timenow); }, tick); } else if (time === 0 && counting === true) { setWorking(!working); if (working === true) { playBreak(); setTime(breakTarget); setClockTarget(breakTarget); } else { playWork(); setCombo(combo + 1); setTime(workTarget); setClockTarget(workTarget); } } if (!counting || config) { clearTimeout(timeout); } }); } export default App;Este no es el código completo. Corté otros componentes para botones y cosas que no se relacionan con esto.
No podemos asegurar que sea EXACTAMENTE 1000 ms. Detrás de escena, las actualizaciones de estado de reacción usan setTimeout , y para cambiar el temporizador, debe usar setTimeout o setInterval . setTimeout y setInterval solo garantizan que el código interno no se ejecutará durante un período de retraso y luego se ejecuta cuando el subproceso principal no está ocupado .
Por lo tanto, es imposible garantizar que cada actualización sea EXACTAMENTE de 1000 ms. Por lo general, habrá un retraso de 30 a 50 ms.
Sin embargo, eso no significa que su temporizador será inexacto o poco confiable. Solo depende de cómo lo inicialices.
A continuación se muestra cómo mejoraría el código que proporcionó, ya que en este momento borrar el tiempo de espera agrega una sobrecarga adicional, y el procesamiento por lotes de las actualizaciones de estado conduciría a un mejor rendimiento en este caso.
let tick = 1000; //countdown modifying behavior every 1000ms. Active clock useEffect(() => { if (time > 0 && counting === true) { setTimeout(() => { setTime((state) => state - 1); console.log(timenow); }, tick); } }, [time, counting]); //pausing, stopping and resuming useEffect(() => { if (time === 0 && counting === true) { setWorking(!working); if (working === true) { playBreak(); unstable_batchedUpdates(() =>{ setTime(breakTarget); setClockTarget(breakTarget); } } else { playWork(); //update all at once unstable_batchedUpdates(() =>{ setCombo(combo + 1); setTime(workTarget); setClockTarget(workTarget); } } } }, [time, counting]);Más mejoras para mantener el reloj sincronizado
setTimeout en su useEffect , utilizando su hora de inicio almacenada y la hora actual, calcule cuánto tiempo queda y establezca el estado en eso. //countdown time modifying behavior every 1000ms const current = new Date() const [start, setStart] = useState(current) //this example uses a 25 minute timer as an example const [end, setEnd] = useState(new Date(start.getTime() + 25 * 60000)) const tick = 1000 useEffect(() => { if (time > 0 && counting === true) { setTimeout(() => { const current = new Date() const diffTime = current.getDate() - end.getDate() const timeRemainingInMins = Math.ceil(diffTime / (1000*60)); setTime(timeRemainingInMins); }, tick); } }, [time, counting]);Puedo tener una idea.
Actualmente, su gancho useEffect está configurado para ejecutarse en cada renderizado, lo que significa que cada vez que cambia la Hora en que renderiza la página y vuelve a ejecutar el gancho useEffect, sin embargo, parece tener bastante lógica dentro del gancho, además, es posible que la reacción no se muestre de manera consistente. lo que significa que puede haber algunas inconsistencias con su tiempo.
Sugiero usar setInterval en su lugar y ejecutar el enlace useEffect solo una vez al principio para configurar el intervalo usando useEffect(() => {...}, []) (observe [] como el segundo argumento)
Para el resto de su lógica, siempre puede crear otro enlace useEffect que se actualice cuando cambien los componentes.
Con todo eso en mente, su lógica de tiempo debería verse así:
useEffect(() => { interval = setInterval(() => { setTime(time-1); console.log(timenow); }, tick); //runs once every tick return(() => clearInterval(interval)); //Once the component unmounts run some code to clear the interval }, [])Ahora, en otro enlace useEffect, puede observar los cambios en el tiempo y actualizar el resto de su aplicación en consecuencia.
Este enfoque debería ser mucho más confiable porque no depende de reaccionar para actualizar el tiempo.
#La solución
const component =() => { const [val, setval]= useState(globalState.get()); useEffect(() => { setVal(globalState.get()); globalState.subscribe(newVal=> setVal(newVal)); }); return <span>{val}</span> }