React — Manejadores de eventos (desde el constructor, campo público de clase, Binding en render(), función de flecha), Pasando argumentos, Eventos sintéticos
Temario
- Introducción
- Datos unidireccionales
- Manejando Eventos (desde el constructor, campo público de clase, Binding en render(), función de flecha)
- Pasando argumentos
- Eventos sintéticos (SyntheticEvent, Eventos soportados)
- Manejando eventos de hijo a padre
i. Introducción
Es momento de ver uno de los tantos pilares que tiene React, entenderemos las diferentes formas de enlazar un evento, por lo que te recomiendo irte despidiendo de addEventListener, ya que con React es mucho más sencillo, así que ya basta de tanta charla y vamos a lo interesante…
ii. Datos unidireccionales
Cuando nunca hemos tocado una biblioteca/framework de front, tenemos la mala práctica de poner datos globales (ej. jQuery) y obtener esos datos desde cualquier lado, lo que esta mal, muy mal!, ya que podemos perder la referencia a ese dato, o al estar tan expuesto alguien lo puede eliminar por error, y romper toda la aplicación. Por fortuna React ha decidido adoptar el patrón de flujo de datos unidireccional, esto quiere decir que los datos solo se pasan por el árbol, de padre a hijo, de arriba hacia abajo; por lo que React es más explícito y restrictivo.
Debemos de tener en cuenta que, sin importar si le pasamos, datos, propiedades (props) o estado (state) para el hijo SIEMPRE serán simples datos; entonces un componente padre o hijo NO puede saber si otro componente tiene o no estado, así como tampoco les va a importar si está definido como una función o clase.
Entonces la única forma de que los demás componentes sepan de los datos, es que se expongan y asignen.
iii. Manejando Eventos
Si nosotros hemos usado controladores de eventos en JavaScript [ref], digamos que con React es más simple que eso, ya que lo vamos a definir todo en el JavaScript y no en el HTML, y estaríamos pasando una función, no un string.
Cuando nosotros creamos un evento[ref] en JavaScript, lo hacemos de la siguiente manera:
En React es un poco diferente, veamos el mismo ejemplo de arriba:
Los primeros cambios que vemos son:
- Los eventos de React se nombran usando camelCase (onclick => onClick)
- React implementa un sistema de eventos sintéticos que proporciona consistencia y alto rendimiento a las aplicaciones de React.
- Con JSX le pasamos una función como el manejador de evento, en vez de un string (“starWars()” => {starWars})
Vamos a realizar un ejemplo sencillo, donde, vamos a ver todas las formas de manejar el evento click…
Ejemplo: Crearemos un componente que tenga un botón, donde cada vez que se le da click, lance una alerta diciendo “Hello Events!!”
- Lo primero que hay que hacer es nuestro componente MyFirstEvent
- El siguiente paso es agregar el evento a nuestro botón:
Podemos observar que en el evento onClick, manda a llamar el método handleClick; ahora viene lo interesante, todas las formas de crear el método y cachar el evento…
a — Desde el constructor
El siguiente paso es muy importante; debemos de hacer dos cosas:
- Agregar nuestro método handleClick a nuestro componente.
- Agregar nuestro método handleClick en el constructor, haciendo el bindeo a this (esto es obligatorio, para que pueda funcionar el callback), su sintaxis es la siguiente:
Hagamos los cambios en nuestro ejemplo:
Veamos el ejemplo completo:
Si nosotros no le indicamos el .bind(this), podemos tener problemas, ya que en JavaScript, los métodos de clase NO están ligados por defecto[ref], en caso de que olvidemos ligar el this a nuestro método, cuando intentemos usar this retornará undefined.
b — Como campo público de clase
En caso de que no quieras agregar el método al constructor y ligar el this, podemos usar la sintaxis experimental de campos públicos de clases[ref], veamos la sintaxis:
Hagamos los cambios en nuestro ejemplo:
Veamos nuestro ejemplo completo:
Importante: Sin duda esta forma es genial, solo hay que tener cuidado, ya que hasta el día de hoy (agosto 2020) sigue siendo experimental, por lo que puede generarte problemas en algunos navegadores.
d — Binding en render()
Podemos vincular el método desde el método render(), esto quiere decir que no va a ser necesario vincularlo desde el constructor, la sintaxis es la siguiente:
Veamos un ejemplo, donde necesitemos utilizar el this, pero al momento de enlazar el evento, lo vamos a hacer de manera básica:
Ejemplo 2: Vamos a crear un componente con un botón, y nos cuente cuántas veces le hemos dado clic, entonces:
Veamos el ejemplo completo:
Cada vez que le damos click, nos sale el siguiente error:
Hagamos el pequeño cambio a nuestro código (agregar el bind):
Veamos el ejemplo completo:
Solo tener cuidado con el rendimiento, ya que la función se re-asigna en cada render, quizás en aplicaciones pequeñas no se note, pero quizás en las grandes si.
c — Como función flecha
Podemos utilizar la función de flecha, que va directamente en el evento de nuestro componente, su sintaxis es la siguiente:
Estoy seguro de que esta va a ser tu sintaxis favorita; solo que, hay que tener mucho cuidado, dependiendo de la arquitectura de nuestros componentes, nos puede dar problemas de rendimiento.
Hagamos los cambios en nuestro ejemplo:
Veamos el ejemplo completo:
Antes de ver el tema de rendimiento, hay un consejo Ryna florence:
If you aren’t measuring, you can’t even know if your optimizations are better, and you certainly won’t know if they make things worse![ref]
Se me hace genial, ya que puede generar retraso en el tiempo de desarrollo (Importante: una cosa es programar mediante buenas prácticas y la otra es programar sin sentido alguno), o la otra es de que al estar enfocados tanto en la optimización que sale contraproducente y generar problemas de rendimiento; de hecho a mi personalmente nos ha sucedido en algunos proyectos, donde una solución “normal” era más rápida, sencilla y mantenible que la misma optimización.
Las funciones en línea son “lentas” principalmente estas razones:
- Cada vez que se crea una arrow function, la función pasada se va a recolector de basura[ref][ref].
- La comparación de propiedades de objeto por igualdad estricta.
- Cada vez que se crea una nueva función, la comparación superficial (React.PureComponent[ref]) lo detecta como un cambio y el componente se vuelve a mostrar.
Ejemplo: Imaginemos que tenemos un dashboard, y en la parte superior tenemos un login:
La primera vez debe darle la oportunidad de iniciar sesión al usuario, cuando lo haya hecho, debe mostrarle su perfil, con la finalidad de poder ir a su perfil o cerrar sesión (nos vamos a enfocar solo en los botones), si nos vemos muy puristas, el código debería de quedar (quizás así):
Veamos el ejemplo completo:
¿Pueden ver donde puede estar la “falla” de rendimiento?, el problema es que he creado 3 manejadores de eventos, si hubiéramos dejado todos en línea solo se carga un manejador de eventos, hasta el momento que se loguea, se generan los otros dos.
El ejemplo está mal hecho a propósito, para que fuera más visual el problema de que no siempre optimizar o de hacer a un lado el problema de rendimiento de una arrow function; es como comenté más arriba, realmente depende de como este la arquitectura de su aplicación y de las necesidades que se tenga.
iv. Pasando argumentos
Cuando tenemos algún loop de componentes o elementos, es muy común querer pasarle algún identificador u otro valor al manejador de eventos, hay dos formas de hacerlo:
- Por medio de una arrow function:
Importante: Cuando enlacemos un evento mediante arrow function, no olvidemos pasar el event (e).
Ejemplo: Imagina que tenemos una lista, y necesitamos obtener el id del item cuando se le da click, entonces:
- Enlazando la función:
Importante: Cuando enlacemos un evento mediante function, no es necesario pasarle el event (e), ya que React lo hace por nosotros.
Ejemplo: Aprovechemos el mismo ejemplo de arriba, solo adaptemos el evento a una función enlazada:
Si revisamos ambos ejemplos, podemos darnos cuenta que al recibir los parámetros es exactamente de la misma forma:
- Primero van todos los parámetros que se mandan desde el evento, y por último el event (e)
v. Eventos sintéticos
a — SyntheticEvent
Los eventos sintéticos (SyntheticEvent[ref]) es una envoltura al evento nativo del navegador, con la finalidad que que funcione en todos los navegadores y están acorde a las especificaciones de la W3C[ref], por lo que no debemos preocuparnos.
Veamos todos los atributos que podemos obtener:
Por razones de rendimiento el evento sintético se limpiará después de que se haya invocado el callback del evento, veamos un ejemplo:
Hay una forma de persistir el evento, para ello debemos ejecutar “event.persist();”, lo que hará es eliminar el evento sintético y nos permitirá utilizar event como referencia del evento y poder usarlo asíncronamente.
Se recomienda usar cuando se necesite persistir las propiedades completas del evento, ya que el rendimiento puede verse un poco afectado.
Veamos un ejemplo:
b — Eventos soportados
Como ya mencionamos, los eventos están normalizados para que puedan ser utilizados en los diferentes navegadores.
Hasta la versión 16.8 de React, veamos la lista y ejemplos de algunos de ellos:
— Eventos del PortaPapeles[ref]
- Eventos:
onCopy, onCut, onPaste
- Propiedades:
clipboardData
— Eventos de Composición
Los eventos de composición, nos ayudan a ingresar texto de manera alternativa a los eventos comunes de teclado, esto quiere decir que se van a activar con caracteres no latinos (ej. acentos, caracteres japoneses, etc), también es útil para seleccionar opciones de palabras de una combinación de pulsaciones en un dispositivo móvil o para convertir comandos de voz en texto (obviamente usando un procesador de reconocimiento de texto)
- Eventos:
onCompositionEnd, onCompositionStart, onCompositionUpdate
- Propiedades:
data
Veamos una animación de cómo se activan los eventos:
Podemos observar que cuando se ingresan caracteres latinos, no lanza ningún evento, hasta que ingresamos caracteres no latinos.
— Eventos del Teclado
- Eventos:
onKeyDown, onKeyPress, onKeyUp
- Propiedades:
altKey, charCode, ctrlKey, key, keyCode, locale, location, metaKey, repeat, shiftKey, which
La propiedad key puede tomar cualquier valor documentado en DOM Level 3 Events[ref]
Veamos una animación de cómo se activan los eventos:
— Eventos de Enfoque
- Eventos:
onFocus, onBlur
- Propiedades:
relatedTarget
Funciona en todos los elementos DOM, incluyendo elementos de formularios.
Veamos una animación de cómo se activan los eventos:
— Eventos Formulario
Voy a hacer una story dedicada a los formularios, por qué es mejor que vayan ahí los ejemplos.
- Eventos:
onChange, onInput, onInvalid, onReset, onSubmit
— Eventos del Ratón
- Eventos:
onClick, onContextMenu, onDoubleClick, onDrag, onDragEnd, onDragEnter, onDragExit,onDragLeave, onDragOver, onDragStart, onDrop, onMouseDown, onMouseEnter, onMouseLeave, onMouseMove, onMouseOut, onMouseOver, onMouseUp
- Propiedades:
boolean, altKey, button, buttons, clientX, clientY, ctrlKey, metaKey, pageX, pageY, relatedTarget, screenX, screenY, shiftKey
Los eventos onMouseEnter
y onMouseLeave
se propagan desde el elemento que se deja hasta el que se ingresa en lugar del bubbling normal y no tienen una fase de captura.
Veamos una animación de cómo se activan algunos eventos:
— Eventos de Imagen
- Eventos:
onLoad, onError
Si quieres ver completa la lista de eventos[ref].
v. Manejando eventos de hijo a padre
Como hemos mencionado React adoptó el patrón de flujo de datos unidireccional, con el fin de comprender el código de manera más sencilla; pero muchas veces, surge la necesidad de pasar datos del hijo al padre; el ejemplo más común son los formularios, cuando la entrada del usuario le afecta al componente padre.
En un principio puede parece ser complicado, pero vamos a hacerlo paso a paso, y se darán cuenta que es más fácil de lo que pensaban.
Imaginemos el siguiente escenario:
Tenemos 3 componentes App, Accordion, Input; la idea es que cada vez que escriban en el Input, el padre (Accordion), reciba el valor y lo imprima en un div.
Algo así:
Pasar el argumento de arriba a abajo es fácil a través de manejadores de eventos, pero devolver datos puede parecer “complicado”.
- Creamos el componente Input, le agregamos el atributo onChange, para que cada vez que escriba mandemos en consola el valor:
- Creamos el componente Accordion, donde vamos a agregar el componente Input
En el componente Accordion, vamos q hacer dos cosas:
- Agregamos un método para manejar el eventos (handleInputOnChange).
- Al componente Input, le pasaremos en el atributo callbackOnChange el método que hemos creado (handleInputOnChange)
Los pasos de arriba es donde va a ocurrir el 50% de la magia, que es básicamente pasale nuestro callback al input, para que el internamente lo ejecute.
Importante: No olvidemos pasarle la referencia del this, ya que sin ella nos arroja errores.
En nuestro componente Input, vamos a validar si nos han mandado un método, en caso de ser así, vamos a regresar el valor:
Este es el otro 50% que hace la magia, que es ejecutar el callback recibimos del padre.
Y por último, creamos un estado en el componente Accordion, para que se vea reflejado en el div, esto quiere decir que debemos hacer:
- Agregar el método construct e inicializar la propiedad en el estado (state)
- En el método handleInputOnChange debemos de actualizar el estado
- Agregar el estado en nuestro JSX
Si hemos realizado todos los pasos correctamente, debemos ver algo así:
Veamos el ejemplo completo:
En la siguiente entrega vamos a ver React — map, filter y reduce
La entrega pasada vimos React — Estado y ciclo de vida de un componente
Bibliografía y links que te puede interesar…