Tengo una clase "Ticker" simple que puede contener una única devolución de llamada y ejecutar esa devolución de llamada cada ~1s a través setInterval El código es el siguiente.
class Ticker{ listner = null; constructor(){ setInterval(this.excuteCallbackInterval, 1000) } addListner(callback){ this.listner = callback } excuteCallbackInterval = () => { if(this.listner){ this.listner(); }else { console.log("no listner registered"); } } } Tengo un React Functional Component en otro archivo, que crea una instancia de un objeto Ticker, a nivel de clase (es decir, fuera del componente funcional) pero dentro del mismo archivo. Aquí está el código para ese componente. (algunas partes se eliminan para que sea conciso)
. . . const ticker = new Ticker() // react component. function TimerDuration({ changingValue }) { const unsubscribe = useRef(null) function runEachSecond() { console.log('inside runEachSecond', changingValue) } useEffect(() => { unsubscribe.current = ticker.addListener(runEachSecond) // Cleanup return () => { try { unsubscribe.current() } catch (err) { // Do nothing } } }, []) return ( <Text> {changingValue} </Text> ) } Mi problema es que cuando el temporizador TimerDuration muestra inicialmente changingValue pintado en la pantalla y excuteCallbackInterval changingValue el mismo,
Pero cuando se actualiza la propiedad ChangingValue, ese valor actualizado no se refleja dentro excuteCallbackInterval pero el changingValue se refleja en el valor pintado en la pantalla.
Mi solución fue (en cierto modo por instinto) almacenar el valor de changingValue en una referencia y actualizarlo dentro de un segundo useEffect que se ejecuta cada vez. Puede que no sea el ideal, de ahí esta pregunta.
Mi pregunta es ¿por qué estoy viendo este comportamiento? runEachSecond se evalúa con cada actualización de prop. Y puede acceder a changingValue porque está en su ámbito principal. La función excuteCallbackInterval dentro del Ticker también tiene acceso a changingValue debido al cierre. Pero, ¿por qué la actualización no se refleja en excuteCallbackInterval ? También hay una mejor manera de resolver este problema?
Mi pregunta es ¿por qué estoy viendo este comportamiento?
runEachSecondse evalúa con cada actualización de prop.
No, no está actualizado. En React, todas las variables permanecen constantes. Se crea una nueva función runEachSecond cada vez que se procesa el componente de la función TimerDuration , y esa nueva función se cerraría sobre el nuevo parámetro changingValue .
Sin embargo, runEachSecond solo se usa en el efecto (en ticker.addListener(runEachSecond) ), que se ejecuta solo una vez (cuando el componente está montado). El ticker almacenará solo la primera de las muchas funciones runEachSecond , y nunca cambiará (no se vuelve a llamar a addListener ), y esa primera función aún se cierra sobre el primer changingValue .
Solo quiero que se suscriba después del montaje y se dé de baja al desmontarlo.
En ese caso, debe almacenar el valor changingValue en la referencia y luego consultar esa referencia (y sus valores futuros) desde la función de escucha de ticker. La referencia para la función de cancelación de unsubscribe devuelta por addListener no es necesaria.
function TimerDuration({ changingValue }) { const value = useRef(null); value.current = changingValue; //^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ useEffect(() => { const unsubscribe = ticker.addListener(function runEachSecond() { console.log('inside runEachSecond', value.current); // ^^^^^^^^^^^^^ }); // Cleanup return () => { try { unsubscribe(); } catch (err) { // Do nothing } } }, []) return ( <Text> {changingValue} </Text> ) }Quiere darse de baja y volver a suscribirse cada vez que changingValue el valor. O, dicho de otra manera: desea actualizar la devolución de llamada del ticker para evitar que se vuelva obsoleto.
useEffect(() => { function runEachSecond() { console.log('inside runEachSecond', changingValue) } const unsubscribe = ticker.addListener(runEachSecond) // Cleanup return () => { try { unsubscribe() } catch (err) { // Do nothing } } }, [changingValue]) // see changingValue in the deps¿Por qué necesitas hacer esto? En este momento, su componente hace algo como esto:
runEachSecond (instancia #1)runEachSecond #1changingValue de valor prop obtener actualizaciónrunEachSecond se crea de nuevo (instancia #2)Ticker todavía tiene una referencia de runEachSecond #1 Después de agregar ChangingValue en las dependencias de changingValue :
runEachSecond (instancia #1)runEachSecond #1changingValue de valor prop obtener actualizaciónrunEachSecond se crea de nuevo (instancia #2)Ticker darse de baja de runEachSecond #1Ticker suscríbase de nuevo, pero para runEachSecond #2