objetivo: crear un componente Clock
que llame a un método de devolución de llamada a intervalos regulares, pero cuya velocidad se pueda controlar.
Parte difícil: no reinicie el temporizador del reloj inmediatamente cuando cambie la velocidad, pero en el siguiente "tick" verifique la velocidad deseada y, si tiene cambios, reinicie el intervalo actual y programe uno nuevo. Esto es necesario para mantener el reloj a un ritmo suave al cambiar la velocidad.
Pensé que pasar una función getDelay
que devuelve el retraso (en lugar del valor del retraso en sí) haría que esto funcionara, pero no es así.
Si dejo que useEffect
rastree la función getDelay
, se restablecerá cuando cambie el retraso. Si no realiza un seguimiento getDelay
, la velocidad no cambiará mientras el reloj esté funcionando.
import React, { useEffect, useRef } from "react"; type Callback = () => void; function useInterval(tickCallback: Callback, getDelay: () => number, isPlaying: boolean) { const refDelay = useRef<number>(getDelay()); useEffect(() => { let id: number; console.log(`run useEffects`); function tick() { const newDelay = getDelay(); if (tickCallback) { console.log(`newDelay: ${newDelay}`); tickCallback(); if (newDelay !== refDelay.current) { // if delay has changed, clear and schedule new interval console.log(`delay changed. was ${refDelay.current} now is ${newDelay}`) refDelay.current = newDelay; clear(); playAndSchedule(newDelay); } } } /** clear interval, if any */ function clear() { if (id) { console.log(`clear ${id}`) clearInterval(id); } } /** schedule interval and return cleanup function */ function playAndSchedule(delay: number) { if (isPlaying) { id = window.setInterval(tick, delay); console.log(`schedule delay id ${id}. ms ${delay}`) return clear } } return playAndSchedule(refDelay.current); }, // with getDelay here the clock is reset as soon as the delay value changes [isPlaying, getDelay]); } type ClockProps = { /** true if playing */ isPlaying: boolean; /** return the current notes per minute */ getNpm: () => number; /** function to be executed every tick */ callback: () => void; } export function Clock(props: ClockProps) { const { isPlaying, getNpm, callback } = props; useInterval( callback, () => { console.log(`compute delay for npm ${getNpm()}`); return 60_000 / getNpm(); }, isPlaying); return (<React.Fragment />); }
puedes usar algo como esto:
import React, { useCallback, useEffect, useMemo, useRef } from 'react'; function useInterval(tickCallback: () => void, delay: number, isPlaying: boolean) { const timeout = useRef<any>(null); const savedDelay = useRef(delay); const savedTickCallback = useRef(tickCallback); useEffect(() => { savedDelay.current = delay; }, [delay]) useEffect(() => { savedTickCallback.current = tickCallback; }, [tickCallback]) const startTimeout = useCallback(() => { const delay = savedDelay.current; console.log('next delay', delay); timeout.current = setTimeout(() => { console.log('delay done', delay); savedTickCallback.current(); startTimeout(); }, savedDelay.current); }, []); useEffect(() => { if (isPlaying) { if (!timeout.current) { startTimeout(); } } else { if (timeout.current) { clearTimeout(timeout.current); } } }, [isPlaying, startTimeout], ); } type ClockProps = { /** true if playing */ isPlaying: boolean; /** return the current notes per minute */ getNpm: () => number; /** function to be executed every tick */ callback: () => void; } export const Clock: React.FC<ClockProps> = ({ isPlaying, getNpm, callback }) => { const delay = useMemo(() => { console.log(`compute delay for npm ${getNpm()}`); return 60_000 / getNpm(); }, [getNpm]); useInterval(callback, delay, isPlaying); return null; };