👉 Ver todas las notas
Antes de empezar a hablar sobre los diferentes conceptos, siempre es útil entender un poco la motivación, por qué existe, qué problema resuelve la herramienta que elegimos. En mi opinión, este proceso resulta además muy beneficioso para entender, tanto mejor como más rápido, los conceptos fundamentales detrás de cualquier tecnología que decidamos usar.
Con los años, la complejidad de las aplicaciones web fue creciendo. Al principio, sólo teníamos archivos HTML, CSS y algo de JS para proveer un mínimo de interacción con el DOM. El código JS que escribíamos no era compatible con todos los browsers por default, por lo que había que escribir diferentes versiones. jQuery surge entonces como una solución a este problema, brindando una API unificada para escribir este código una vez y de forma más simple, garantizando compatibilidad con los diferentes browsers.
Pero la complejidad de las bases de código seguía aumentando y los programas eran cada vez más difíciles de mantener. El framework AngularJS aparece en el 2010 y se posiciona rápidamente como el nuevo standard para construir SPAs (single-page applications), aportando una estructura y el uso de ciertos patrones (MVC) para organizar mejor nuestras aplicaciones.
Más aún, en aplicaciones donde acciones en diferentes partes de la UI tenían efectos sobre otras[1], los problemas seguían estando y nos encontrábamos con aplicaciones escritas en Angular donde era difícil entender el flujo de los datos y qué parte del código afectaba a cuál otra.
Facebook tenía este problema muy presente, por lo que decidieron desarrollar una alternativa. En el 2013, Facebook libera React, una biblioteca de JavaScript para construir interfaces de usuario, según definen en el sitio oficial.
La manipulación del DOM es uno de los principales cuellos de botella en la performance del front-end. React decide entonces tomar un enfoque más declarativo y busca evitar que el browser esté continuamente realizando operaciones costosas.
Por lo tanto, sólo vamos a encargarnos de diseñar las vistas para cada estado de nuestra aplicación y React va a actualizar y renderizar de manera eficiente los componentes correctos cuando los datos cambien (estado), haciendo cambios mínimos en el DOM. El código declarativo es más predecible y por lo tanto, más fácil de de razonar y debuggear.
👉 La vista pasa a ser entonces una función del estado de la aplicación, es decir, cuando el estado de la aplicación cambia, la vista se vuelve a renderizar. Por lo tanto, si queremos que la vista (UI) sea actualice, tenemos que modificar el estado de alguna forma.
Ya no necesitamos preocuparnos por cómo manipular el DOM o manejar eventos del mismo, React se va a encargar de abstraernos de estos detalles.
Vamos a construir interfaces de usuario (UI) utilizando componentes reutilizables, que poseen y manejan un estado propio. Usamos estos componentes como si fueran bloques de Lego, para construir componentes más complejos y eventualmente una aplicación entera.
👉 Llamamos estado a las características propias de un componente. Por ejemplo, cuando tenemos un componente que hace requests a un server, puede tener dos estados posibles, pendiente o finalizado.
La lógica de los componentes se escribe en JavaScript (y no utilizando templates, como es el caso de otras libs/frameworks de front), por lo que podemos pasar datos (props) de forma simple y mantener el estado fuera del DOM.
Componente
👉 Un componente es un bloque de código reutilizable, una pieza de UI con contenido, estilos y comportamiento definidos: contiene todo el HTML, CSS y JS necesario para funcionar.
Por ejemplo, una barra de búsqueda es un componente, porque tiene una función independiente, una botón podría también a ser un componente, porque cumple una función. Básicamente, cualquier sección de la UI puede llegar a ser un componente.
Por lo tanto, en React, cada parte de la UI es un componente y cada componente tiene un estado.
Si el estado de nuestra aplicación indica por ejemplo, que un usuario se encuentra logueado, crearemos los componentes correspondientes basados en esa información.
👉 Los componentes entonces, no dejan de ser simples funciones de JavaScript que reciben esta información a través de diferentes parámetros a los que llamaremos props (por propiedades) y retornan el código necesario (usando JSX) para renderizar los componentes. Las props son inmutables y siempre se pasan de componentes superiores a componentes inferiores.
👉 En React, los datos tienen 1 y sólo 1 forma (o dirección) de ser transferidos hacia otras partes de la aplicación. Esto implica que los componentes hijos (child components) no pueden actualizar los datos que provienen de un componente padre (parent component).
Los datos que vienen de un componente padre se conocen como props.
El principal beneficio de tomar este approach, en el que los datos fluyen a través de nuestra aplicación en una única dirección, es que el código resulta más fácil de razonar y debuggear, porque sabemos qué datos provienen de dónde y por lo tanto menos propenso a errores.
👉 Cualquier cambio que se le realice al state de un componente, sólo puede afectar a los componentes que están debajo (los child components), que van a recibirlo como props de sólo lectura.
Como los datos se mueven en una única dirección, modificar el estado de un componente no afecta a su componentes padre o hermanos: sólo los descendientes van a ser afectados (un child component no puede modificar el state de su parent component). Esta es la principal razón por la que el state suele levantarse (lo movemos "hacia arriba" en el árbol de componentes), de manera tal que pueda compartirse y ser accedido entre los componentes que lo necesitan.
Actualizar y volver a renderizar el DOM en el browser cada vez que queremos realizar un cambio en la UI tiene un gran impacto en la performance de nuestra aplicación, porque implica realizar operaciones costosas. Al hacer cambios en el DOM, el elemento modificado y sus descendientes (children) deben volver a renderizarse para que el cambio se vea reflejado en la UI. Realizar estas operaciones continuamente (re-rendering, re-painting, etc) es lo que vuelven lenta y poco eficiente a esta forma de trabajar.
Con React no hacemos cambios al DOM directamente, en cambio, esta librería propone utilizar una alternativa, el Virtual DOM.
👉 El Virtual DOM es una representación virtual del DOM, una "versión liviana" (un gran objeto JS) del DOM real que encontramos en el browser, que React utiliza para mapear elementos del DOM real y poder realizar cambios en este de una forma mucho más rápida y eficiente. Cada vez que el state de nuestra aplicación cambia, se actualiza el Virtual DOM y no el DOM real.
👉 Básicamente, cada vez que agregamos nuevos elementos (componentes) a la UI, un nuevo virtual DOM (representado como un árbol) es creado. Cada elemento es un nodo de este árbol. React toma un snapshot de los elementos de nuestra aplicación y lo carga en este árbol. Si el state de alguno de estos elementos cambia, se genera un nuevo virtual DOM. Este DOM (virtual) es entonces comparado con el DOM (virtual) previo y se calculan las diferencias a través de un algoritmo de diffing. Utilizando esta información, React calcula la forma más eficiente de realizar los cambios en el DOM real, actualizando sólo los nodos que cambiaron, reduciendo el impacto en la performance de nuestra aplicación.
Otra estrategia que utiliza React para mejorar la performance es enviar los cambios detectados en el virtual DOM por lotes (batch), para luego realizar los cambios necesarios en el DOM real de una vez, en lugar de estar enviando continuamente updates al mismo por cada cambio del estado.
👉 En resumen:
- realizar updates frecuentes del DOM (real) es costoso y tiene un gran impacto en la performance
- el Virtual DOM es una representación virtual del DOM real que React utiliza para mejorar la performance
- cuando ocurre un cambio en el state, se genera un nuevo virtual DOM y se compara con la versión anterior. Esto se conoce como diffing
- los cambios a realizar en el DOM se envían por tandas, para actualizar la UI con menor frecuencia y por lo tanto, menor costo
👉 React es una librería (o biblioteca) que sólo se encarga de resolver un problema: renderizar la vista o UI de nuestra aplicación.
A diferencia de otros frameworks de front-end, como Angular, Vue o Svelte, React no es opinionado, no asume nada sobre nuestro stack tecnológico ni sobre cómo resolver y conectar el resto de las partes; esas decisiones quedarán por nuestra cuenta. Gracias a esto, la API de React resulta más concisa y simple en comparación y por lo tanto, más simple de aprender.
Además, esta característica permite también que podamos reutilizar código React en diferentes plataformas: por ejemplo, renderizando desde el servidor usando Node o en aplicaciones móviles, a través de React Native.
Como devs, tendremos que tomar varias decisiones relacionadas a la arquitectura de la aplicación, que podrían resumirse en los siguientes puntos:
- definir los React components
- definir qué datos forman parte del state y dónde (en qué componente) va a vivir
- decidir qué cambios deben realizarse en la UI cuando el state cambia
1 Estos problemas existieron (y todavía existen) en las librerías que decidieron usar 2-way data binding (o flujo de datos bidireccional). Es decir, los cambios en la UI afectan al objeto JS que la UI quiere representar y viceversa.