Tengo un componente de clase React/Redux que estoy convirtiendo en un componente funcional.
Anteriormente, tenía una devolución de llamada de componentDidMount que agregaba un detector de eventos para un evento personalizado enviado desde otra parte de la aplicación. Esto está siendo reemplazado por useEffect . El detector de eventos, al activarse, llama a un método en otro lugar de este componente.
Este método realiza acciones que dependen de los valores recuperados de un selector. Inicialmente, estaba pasando useEffect una matriz vacía de dependencias para que solo agregara el detector de eventos en el montaje. Sin embargo, obviamente esto da como resultado un cierre obsoleto en torno a los valores del selector. Una solución funcional es pasarle el selector como una dependencia; sin embargo, esto da como resultado que el oyente se elimine/vuelva a agregar cada vez que cambie el valor del selector, lo cual no es bueno.
Estoy tratando de pensar en una solución que solo agregue el detector de eventos una vez y permita que el método llamado acceda al valor actual del selector.
Código de ejemplo:
const currentValue = useSelector(state => getValue(state)); useEffect(() => { document.addEventListener('my.custom.event', handleEvent); return(() => document.removeEventListener('my.custom.event', handleEvent)); }, []); const handleEvent = () => { console.log(currentValue) } Tal como está, esto crea un cierre obsoleto en torno a currentValue de modo que, en el desencadenante del evento, el valor registrado no es necesariamente el más reciente.
Cambiar [] a [currentValue] en useEffect da como resultado el comportamiento esperado, pero elimina/vuelve a agregar el detector de eventos en cada cambio de currentValue .
Dado que este no es un valor de estado del componente, no hay opción para usar una devolución de llamada como console.log(currentValue => console.log(currentValue)) para acceder al valor más reciente. También jugué con el uso de useRef para mantener el valor, pero creo que necesitaría alguna forma de actualizar el valor de referencia cada vez que cambia el valor del selector, lo cual no es una gran solución.
En el componente real, el valor de currentValue se modifica en Redux por otros componentes, por lo que cambiarlo a un valor de estado tampoco es viable.
Me pregunto:
componentDidMount no sufre el problema de cierre obsoleto).El método useRef suele ser la solución para este problema, pero necesitará otro useEffect para actualizar la referencia con el currentValue :
const currentValue = useSelector(state => getValue(state)); const valueRef = useRef(); useEffect(() => { valueRef.current = currentValue; }, [currentValue]); useEffect(() => { const handleEvent = () => { console.log(valueRef.current) } document.addEventListener('my.custom.event', handleEvent); return(() => document.removeEventListener('my.custom.event', handleEvent)); }, []); Sin embargo, también puede extraer la función completa de useEffect y usarla en un enlace, de modo que pueda crear fácilmente un enlace personalizado para el manejo de eventos:
const useEventHandler = (eventName, eventHandler, eventTarget = document) => { const eventHandlerRef = useRef(); useEffect(() => { eventHandlerRef.current = eventHandler; }); useEffect(() => { const handleEvent = (...args) => eventHandlerRef?.current(...args); eventTarget.addEventListener(eventName, handleEvent); return (() => eventTarget.removeEventListener(eventName, handleEvent)); }, []); }Usando el gancho:
const currentValue = useSelector(state => getValue(state)); useEventHandler('my.custom.event', () => console.log(currentValue))sin embargo, esto da como resultado que el oyente se elimine o vuelva a agregar cada vez que cambie el valor del selector, lo cual no es muy bueno.
Por qué es un problema? ¿Afecta el rendimiento de su aplicación de alguna manera medible? Desde el punto de vista de la inmutabilidad, si el valor cambia, entonces el detector de eventos hace algo diferente, por lo que tiene sentido simplemente reemplazar el detector de eventos por uno diferente.
Si el valor cambia muchas veces por segundo (por ejemplo, en cada cuadro o cada movimiento del mouse), entonces crea un pequeño impacto en el rendimiento, en cuyo caso la solución de Ori Drori podría ser buena, pero hace que el código sea más difícil de entender y el la ganancia de rendimiento es bastante pequeña.
Pero si el valor solo cambia unas pocas veces por segundo o con menos frecuencia, no me preocuparía en absoluto por agregar y eliminar el detector de eventos.