Separar Eventos de Efectos
Los controladores de eventos solo se vuelven a ejecutar cuando realizas la misma interacción de nuevo. A diferencia de los controladores de eventos, los Efectos se resincronizan si algún valor que leen, como una propiedad o una variable de estado, es diferente al que era durante el último renderizado. A veces, también quieres una mezcla de ambos comportamientos: un Efecto que se vuelve a ejecutar en respuesta a algunos valores pero no a otros. Esta página te enseñará cómo hacerlo.
Aprenderás
- Cómo elegir entre un controlador de eventos y un Efecto
- Por qué los Efectos son reactivos y los controladores de eventos no lo son
- Qué hacer cuando quieres que parte del código de tu Efecto no sea reactivo
- Qué son los Eventos de Efecto y cómo extraerlos de tus Efectos
- Cómo leer las propiedades y el estado más recientes desde los Efectos usando Eventos de Efecto
Elegir entre controladores de eventos y Efectos
Primero, recapitulemos la diferencia entre controladores de eventos y Efectos.
Imagina que estás implementando un componente de sala de chat. Tus requisitos se ven así:
- Tu componente debe conectarse automáticamente a la sala de chat seleccionada.
- Cuando haces clic en el botón "Enviar", debe enviar un mensaje al chat.
Digamos que ya has implementado el código para ellos, pero no estás seguro de dónde ponerlo. ¿Deberías usar controladores de eventos o Efectos? Cada vez que necesites responder a esta pregunta, considerapor qué el código necesita ejecutarse.
Los controladores de eventos se ejecutan en respuesta a interacciones específicas
Desde la perspectiva del usuario, enviar un mensaje debería ocurrirporquese hizo clic en el botón "Enviar" específico. El usuario se molestará bastante si envías su mensaje en cualquier otro momento o por cualquier otra razón. Por eso, enviar un mensaje debería ser un controlador de eventos. Los controladores de eventos te permiten manejar interacciones específicas:
Con un controlador de eventos, puedes estar seguro de quesendMessage(message)se ejecutarásolosi el usuario presiona el botón.
Los Efectos se ejecutan cuando se necesita sincronización
Recuerda que también necesitas mantener el componente conectado a la sala de chat. ¿Dónde va ese código?
Larazónpara ejecutar este código no es una interacción particular. No importa por qué o cómo el usuario navegó a la pantalla de la sala de chat. Ahora que la están viendo y podrían interactuar con ella, el componente necesita permanecer conectado al servidor de chat seleccionado. Incluso si el componente de la sala de chat fuera la pantalla inicial de tu aplicación y el usuario no ha realizado ninguna interacción,aúnnecesitarías conectarte. Por eso es un Efecto:
Con este código, puedes estar seguro de que siempre hay una conexión activa al servidor de chat actualmente seleccionado,independientementede las interacciones específicas realizadas por el usuario. Ya sea que el usuario solo haya abierto tu aplicación, seleccionado una sala diferente o navegado a otra pantalla y vuelto, tu Efecto asegura que el componentepermanecerá sincronizadocon la sala actualmente seleccionada yse reconectará cuando sea necesario.
Valores reactivos y lógica reactiva
Intuitivamente, podrías decir que los controladores de eventos siempre se activan "manualmente", por ejemplo, al hacer clic en un botón. Los Efectos, por otro lado, son "automáticos": se ejecutan y vuelven a ejecutar tantas veces como sea necesario para mantenerse sincronizados.
Hay una forma más precisa de pensar en esto.
Las props, el estado y las variables declaradas dentro del cuerpo de tu componente se llamanvalores reactivos. En este ejemplo,serverUrlno es un valor reactivo, peroroomId y messagesí lo son. Participan en el flujo de datos del renderizado:
Valores reactivos como estos pueden cambiar debido a una nueva renderización. Por ejemplo, el usuario puede editar elmessageo elegir unroomIddiferente en un menú desplegable. Los controladores de eventos y los Efectos responden a los cambios de manera diferente:
- La lógica dentro de los controladores de eventosno es reactiva.No se ejecutará de nuevo a menos que el usuario realice la misma interacción (por ejemplo, un clic) otra vez. Los controladores de eventos pueden leer valores reactivos sin "reaccionar" a sus cambios.
- La lógica dentro de los Efectoses reactiva.Si tu Efecto lee un valor reactivo,debes especificarlo como una dependencia.Entonces, si una nueva renderización provoca que ese valor cambie, React volverá a ejecutar la lógica de tu Efecto con el nuevo valor.
Revisemos el ejemplo anterior para ilustrar esta diferencia.
La lógica dentro de los controladores de eventos no es reactiva
Observa esta línea de código. ¿Esta lógica debería ser reactiva o no?
Desde la perspectiva del usuario,un cambio en elmessage nosignifica que quiera enviar un mensaje.Solo significa que el usuario está escribiendo. En otras palabras, la lógica que envía un mensaje no debería ser reactiva. No debería ejecutarse de nuevo solo porque elvalor reactivoha cambiado. Por eso pertenece al controlador de eventos:
Los controladores de eventos no son reactivos, por lo quesendMessage(message)solo se ejecutará cuando el usuario haga clic en el botón Enviar.
La lógica dentro de los Efectos es reactiva
Ahora volvamos a estas líneas:
Desde la perspectiva del usuario,un cambio en elroomIdsísignifica que quiere conectarse a una sala diferente.En otras palabras, la lógica para conectarse a la sala debería ser reactiva. Túquieresque estas líneas de código "se mantengan al día" con elvalor reactivo, y que se ejecuten de nuevo si ese valor es diferente. Por eso pertenece a un Efecto:
Los Efectos son reactivos, por lo quecreateConnection(serverUrl, roomId) y connection.connect()se ejecutarán para cada valor distinto deroomId. Tu Efecto mantiene la conexión del chat sincronizada con la sala seleccionada actualmente.
Extrayendo lógica no reactiva de los Efectos
Las cosas se complican más cuando quieres mezclar lógica reactiva con lógica no reactiva.
Por ejemplo, imagina que quieres mostrar una notificación cuando el usuario se conecta al chat. Lees el tema actual (oscuro o claro) de las props para poder mostrar la notificación en el color correcto:
Sin embargo,themees un valor reactivo (puede cambiar como resultado de un nuevo renderizado), ycada valor reactivo leído por un Efecto debe declararse como su dependencia.Ahora tienes que especificarthemecomo una dependencia de tu Efecto:
Prueba este ejemplo y observa si puedes detectar el problema con esta experiencia de usuario:
Cuando cambia elroomId, el chat se reconecta como era de esperar. Pero comothemetambién es una dependencia, el chattambiénse reconecta cada vez que cambias entre el tema oscuro y el claro. ¡Eso no es bueno!
En otras palabras,noquieres que esta línea sea reactiva, aunque esté dentro de un Efecto (que es reactivo):
Necesitas una forma de separar esta lógica no reactiva del Efecto reactivo que la rodea.
Declarar un Evento de Efecto
Usa un Hook especial llamadouseEffectEventpara extraer esta lógica no reactiva de tu Efecto:
Aquí,onConnectedse denomina unEvento de Efecto.Es parte de la lógica de tu Efecto, pero se comporta más como un manejador de eventos. La lógica dentro de él no es reactiva, y siempre "ve" los últimos valores de tus props y estado.
Ahora puedes llamar al Evento de EfectoonConnecteddesde dentro de tu Efecto:
Esto resuelve el problema. Observa que tuviste queeliminarthemede la lista de dependencias de tu Efecto, porque ya no se usa en el Efecto. Tampoco necesitasagregaronConnecteda ella, porquelos Eventos de Efecto no son reactivos y deben omitirse de las dependencias.
Verifica que el nuevo comportamiento funcione como esperas:
Puedes pensar en los Eventos de Efecto como muy similares a los manejadores de eventos. La principal diferencia es que los manejadores de eventos se ejecutan en respuesta a interacciones del usuario, mientras que los Eventos de Efecto los activas tú desde los Efectos. Los Eventos de Efecto te permiten "romper la cadena" entre la reactividad de los Efectos y el código que no debería ser reactivo.
Leer las props y el estado más recientes con Eventos de Efecto
Los Eventos de Efecto te permiten corregir muchos patrones en los que podrías sentirte tentado a suprimir el linter de dependencias.
Por ejemplo, supón que tienes un Efecto para registrar las visitas a la página:
Más tarde, agregas múltiples rutas a tu sitio. Ahora tu componentePagerecibe una propurlcon la ruta actual. Quieres pasar laurlcomo parte de tu llamada alogVisit, pero el linter de dependencias se queja:
Piensa en lo que quieres que haga el código.Quieresregistrar una visita separada para diferentes URLs, ya que cada URL representa una página diferente. En otras palabras, esta llamada alogVisit deberíaser reactiva con respecto a laurl. Por eso, en este caso, tiene sentido seguir al linter de dependencias y agregarurlcomo una dependencia:
Ahora digamos que quieres incluir el número de artículos en el carrito de compras junto con cada visita a la página:
UsastenumberOfItemsdentro del Efecto, por lo que el linter te pide que lo agregues como dependencia. Sin embargo,no quieresque la llamada alogVisitsea reactiva con respecto anumberOfItems. Si el usuario pone algo en el carrito de compras y elnumberOfItemscambia, estono significaque el usuario haya visitado la página nuevamente. En otras palabras,visitar la páginaes, en cierto sentido, un "evento". Ocurre en un momento preciso en el tiempo.
Divide el código en dos partes:
Aquí,onVisites un Evento de Efecto. El código dentro de él no es reactivo. Por eso puedes usarnumberOfItems(¡o cualquier otro valor reactivo!) sin preocuparte de que haga que el código circundante se vuelva a ejecutar ante cambios.
Por otro lado, el Efecto en sí sigue siendo reactivo. El código dentro del Efecto usa la propurl, por lo que el Efecto se volverá a ejecutar después de cada nuevo renderizado con unaurldiferente. Esto, a su vez, llamará al Evento de EfectoonVisit.
Como resultado, llamarás alogVisitpor cada cambio en laurl, y siempre leerás elnumberOfItemsmás reciente. Sin embargo, sinumberOfItemscambia por sí solo, esto no causará que se vuelva a ejecutar ningún código.
Nota
Quizás te preguntes si podrías llamar aonVisit()sin argumentos y leer laurldentro de ella:
Esto funcionaría, pero es mejor pasar estaurlal Evento de Efecto explícitamente.Al pasarurlcomo argumento a tu Evento de Efecto, estás diciendo que visitar una página con unaurldiferente constituye un "evento" separado desde la perspectiva del usuario.LavisitedUrles unapartedel "evento" que ocurrió:
Dado que tu Evento de Efecto "pide" explícitamente lavisitedUrl, ahora no puedes eliminar accidentalmente laurlde las dependencias del Efecto. Si eliminas la dependencia deurl(haciendo que las visitas a páginas distintas se cuenten como una), el linter te advertirá al respecto. Quieres queonVisitsea reactivo con respecto a laurl, así que en lugar de leer laurldentro (donde no sería reactiva), la pasasdesdetu Efecto.
Esto se vuelve especialmente importante si hay lógica asíncrona dentro del Efecto:
Aquí, laurldentro deonVisitcorresponde a laúltimaurl(que podría haber cambiado ya), perovisitedUrlcorresponde a laurlque originalmente causó que este Efecto (y esta llamada aonVisit) se ejecutara.
Limitaciones de los Eventos de Efecto
Los Eventos de Efecto tienen limitaciones muy estrictas sobre cómo puedes usarlos:
- Solo llámalos desde dentro de Efectos.
- Nunca los pases a otros componentes o Hooks.
Por ejemplo, no declares y pases un Evento de Efecto así:
En su lugar, declara siempre los Eventos de Efecto directamente junto a los Efectos que los usan:
Los Eventos de Efecto son "fragmentos" no reactivos de tu código de Efecto. Deben estar junto al Efecto que los utiliza.
Recapitulación
- Los controladores de eventos se ejecutan en respuesta a interacciones específicas.
- Los Efectos se ejecutan cuando se necesita sincronización.
- La lógica dentro de los controladores de eventos no es reactiva.
- La lógica dentro de los Efectos es reactiva.
- Puedes mover la lógica no reactiva de los Efectos a los Eventos de Efecto.
- Solo llama a los Eventos de Efecto desde dentro de Efectos.
- No pases Eventos de Efecto a otros componentes o Hooks.
Try out some challenges
Challenge 1 of 4:Fix a variable that doesn’t update #
This Timer component keeps a count state variable which increases every second. The value by which it’s increasing is stored in the increment state variable. You can control the increment variable with the plus and minus buttons.
However, no matter how many times you click the plus button, the counter is still incremented by one every second. What’s wrong with this code? Why is increment always equal to 1 inside the Effect’s code? Find the mistake and fix it.
