React. A vueltas con los "rerenders" (I)
En este artículo podrás aprender todo lo que hay detrás de un "rerender" en React: qué los provoca, cómo evitarlos y patrones y antipatrones a tener en cuenta para optimizar nuestras aplicaciones.
En mi opinión, entender los conceptos clave de una librería es fundamental si queremos sentirnos a gusto trabajando con ella. Por eso he querido escribir un artículo centrado en la piedra angular sobre la que React fue creada: los “renders”. Pese a la evolución que está experimentando en los últimos meses, React no se ha separado de esa idea inicial por la cual creamos interfaces por medio de componentes que devuelven JSX.
Comprender este proceso que conocemos como “renderizado” y los eventos que lo desencadenan es clave si queremos diseñar aplicaciones optimizadas a la vez que sentamos las bases para seguir mejorando. De un modo o de otro, cuando trabajamos con React terminamos hablando de “renders”, así que, ¿por qué no aprender bien cómo funcionan?
He decidido dividir en dos partes el contenido de este artículo sobre React y los “rerenders” para que así no quede excesivamente largo y tengáis tiempo de procesar la información. Para recibir la siguiente parte cuando la publique puedes quedarte suscrito con el siguiente botón:
¿Qué es un “render” en React?
Empecemos por el principio y veamos lo que significa el concepto de “render”. Para mí, un “render” es el proceso por el cual React envía al DOM el JSX devuelto por un componente . El componente calcula este JSX a partir de 3 elementos:
Las propiedades recibidas.
Su estado.
El valor de los contextos que consuma procedentes la API Context.
Un componente es renderizado siempre y cuando aparezca en algún lado del árbol de componentes que hayamos generado.
Vayamos al principio. Cuando el navegador carga por primera vez nuestra aplicación, React genera un árbol de componentes que irán devolviendo trozos de JSX hasta definir la estructura entera de la vista de la aplicación. A medida que el usuario interactúe con esta vista, este árbol se irá modificando de distintas maneras:
Añadiendo nuevos componentes.
Eliminando componentes existentes.
O modificando el estado de los ya existentes.
Lo más importante aquí es que cuando un componente aparece por primera vez en el árbol experimenta un primer renderizado que podemos identificar como “render inicial”. En este render inicial el componente definirá los valores de partida de su estado. A partir de aquí es cuando entra en juego el concepto de “rerender” y todo se vuelve mucho más interesante.
Qué son los “rerenders”
Un “rerender” es cualquier renderizado de un componente que ya se encuentra en el DOM y que no corresponda al “render” inicial.
Un “rerender” sucede cuando se produce un cambio en el estado de la aplicación, bien por la acción del usuario sobre un elemento, bien por la llegada de datos desde fuera como una llamada fetch.
Podemos clasificar los “rerenders” en dos grandes tipos:
Los necesarios, es decir, aquellos en los que el componente se ve afectado directamente por el cambio y, en consecuencia, generará un JSX distinto del anterior. Por ejemplo, si estamos modelando un contador, cuando el usuario pulse el botón que permite incrementarlo, necesitaremos un nuevo render para mostrar el nuevo valor del contador.
Y, los inncesarios, es decir, aquellos en los que el componente se ve obligado a renderizarse de nuevo pese a que el cambio no le afectaba y, por tanto, devolverá el mismo JSX que la vez anterior. Esto habitualmente está motivado por una estructura deficiente de nuestra aplicación.
Por suerte, React está lo suficientemente bien optimizado como para que los “rerenders” innecesarios no penalicen en exceso. Sin embargo, si descuidamos por completo este punto, terminaremos creando aplicaciones imposibles de usar. Lo cual nos lleva directos al siguiente punto… ¿qué provoca un “rerender”?
¿Qué provoca un “rerender”?
Un componente puede volver a renderizarse por alguna de las siguientes razones:
Un cambio en el estado interno declarado mediante
useState
como consecuencia de una acción del usuario, por ejemplo, pulsar un botón.En el caso de consumir un contexto, un cambio en el valor del mismo.
Su componente padre vuelve a renderizarse.
Veámoslas en detalle.
Cambios en el estado
Cuando declaramos una pieza de estado dentro de un componente por medio del hook useState,
cualquier cambio en el mismo como consecuencia de la invocación de setState
provocará un nuevo render del componente.
Cambio en el contexto
Cuando el valor del contexto cambia, todos aquellos componentes que estén consumiendo ese contexto por medio del hook useContext
volverán a renderizarse para así actualizar la vista de acuerdo al cambio producido.
Render del componente padre
Por defecto, un componente vuelve a renderizarse siempre que su componente padre lo haga. Dicho de otro modo, cuando un componente se renderiza, esto supone que todos sus componentes hijos también lo hagan.
Cómo evitar “rerenders” en React
Ahora que ya hemos visto las 3 razones por las que un componente puede volver a renderizarse, veamos algunas de las técnicas que pueden ayudarnos a evitar “rerenders” innecesarios, de modo que podamos optimizar nuestra aplicación lo máximo posible.
Lo bueno de estas técnicas que veremos a continuación es que se pueden aplicar sin importar el nivel que tengas, de modo que si estás comenzando a familiarizarte con los mundos de React te animo a que pierdas el miedo y las pongas en práctica.
Estas técnicas las dividiremos en 3 grandes bloques:
Técnicas mediante composición.
Composición
Situar el estado lo más abajo posible
Esta técnica podemos aplicarla si estamos trabajando con componentes pesados que definen multitud de estados, ya que puede que alguno de ellos sólo se esté usando en una parte muy específica del árbol de componentes.
En la imagen, el estado número 4 sólo se usa en el componente 4. Sin embargo, con esa estructura, un cambio en el estado 4 provocará un render de todo el árbol de componentes (recuerda, un render en un componente provoca el render de todos sus hijos), lo cual es muy ineficiente.
Este problema podemos evitarlo definiendo el estado 4 en Component4
, de modo que sus cambios no afecten a todo el árbol.
Pasar componentes como propiedades
Esta técnica es muy similar a la anterior. Supongamos que estamos pintando un componente que es muy costoso de renderizar dentro de un componente que sufre renders frecuentemente (por ejemplo, un componente que está gestionando un input
):
Esta estructura supone que cada render de FormComponent
provoque el renderizado de los inputs y el componente SlowComponent
.
La forma de evitar un posible problema de rendimiento es aprovecharnos de la propiedad children
, ya que al ser eso, una propiedad, no se verá afectada por los renders que se provoque en el componente FormComponent:
es como si ya llegase renderizada y por tanto React no necesita renderizarla cada vez que FormComponent
lo haga.
Esta técnica se conoce como “envolver el estado alrededor de los hijos” y es muy útil para resolver problemas de rendimiento como el que estamos viendo.
Además, esto no sólo funciona con la propiedad children
, sino que podemos definir tantas propiedades en nuestro componente como necesitemos.
¡Cuidado! Crear un componente dentro de otro
Finalmente, tenemos que tener cuidado con un antipatrón que puede ocasionarnos graves problemas de rendimiento: definir un componente dentro de otro.
Hacer esto supone que React remonte en cada render el componente OtherComponent
, es decir, destruya su anterior versión y vuelva a crearla desde cero. Esto es mucho más costoso a nivel de rendimiento que un simple render. De este antipatrón se derivan problemas como:
“Flashazos”,
La reinicialización del estado de
OtherComponent
en cada render.La ejecución de los efectos declarados por
OtherComponent
en cada render, aunque no tengan dependencias.Pérdida del foco de los
inputs
declarados dentro deOtherComponent
.
Vamos, ¡una fiesta!
🙏🏻 Gracias a Garaje de Ideas
Los amigos de Garaje de ideas patrocinan Latte and Code y buscan talento: http://bit.ly/garaje-tech-talento
Para aprender más
https://www.developerway.com/posts/react-re-renders-guide
Conclusiones
Y hasta aquí la primera parte del artículo que he preparado sobre React y los rerenders. Espero que te haya servido para entender mejor cómo funciona este proceso y de paso te de ayude a resolver algún que otro problema de rendimiento.
Si te animas a contarme tus experiencias con algunos de los patrones propuestos o te has quedado con alguna duda, soy todo oídos.
💛 ¡Que tengas muy buen día!
Hola! Este artículo parece estar basado en este otro publicado en fecha anterior:
https://www.developerway.com/posts/react-re-renders-guide
¿Quizá sería interesante hacer referencia al original?
Está publicado la segunda parte de este artículo? me ha parecido muy interesante... Gracias!