Gestión simple de estado en frameworks declarativos

En un anterior artículo sobre el estado en frameworks declarativos, vimos en que consiste el estado en este tipo de frameworks o librerías como pueden ser React o Flutter y los tipos de estado que existen.

También vimos que es importante gestionarlo cuando el estado se compartía entre varios componentes y es un reto importante.

Existen diferentes técnicas, librerías y patrones para realizar esta gestión de estado.

En este artículo vamos a ver la forma más simple de gestión de estado en entornos declarativos sin utilizar ninguna librería externa y de forma conceptual sin ver ejemplos de código, en futuros artículos veremos ejemplos en diferentes tecnologías tanto web como mobile.

Contexto

Un ejemplo donde es necesario compartir estado es un carrito de la compra.

shopping_cart

Imaginad que en la barra superior en lugar de aparecer solo el icono del carrito, también apareciera el precio total y número de productos totales que tenemos en el carrito.

Dejando a un lado headers y footers, en este ejemplo podríamos tener los siguientes componentes relacionados con la información del carrito:
ProductList, Product, Cart, CartSummary

shopping_cart_components

Introduciendo el problema

De una forma u otra todos estos componentes están relacionados con el carrito de la compra.

Escenarios:

  • Cuando pulsas el boton añadir a carrito en un producto, se debería ver refrejado esta acción en el cart summary de la app bar, mostrando el precio total y número de productos. También si visualizo el carrito debería tener este cambio incluido.
  • Cuando pulsas el boton añadir a carrito en un producto que ya existe en el carrito, se debería ver refrejado esta acción en el cart summary de la app bar, incrementando el número de productos y el total. También si visualizo el carrito debería tener este cambio incluido incrementando la cantidad del producto en el carrito y el total.
  • Cuando eliminas un producto del carrito este cambio se debería ver reflejado en el importe total del carrito, en el listado del mismo carrito y el total. También en el cart summary de la app bar se debe reflejar el cambio.
  • Cuando modificas la cantidad de un elemento del carrito este cambio se debería ver reflejado en el propio total del carrito y en el cart summary de la app bar.

shopping_cart_state

El problema es que cada componente no puede tener su propia instancia del carrito porque entonces los cambios que realiza un componente en el carrito no se ven reflejados en los demás componentes.

Tampoco es una solución que me guste que cada componente realice una consulta a la base de datos o a la api para recuperar datos actualizados del carrito cuando hay cambios.

Los problemas siempre deberíamos resolverlos donde se producen, en este caso el problema nos lo plantea el framework o librería de presentación al ser declarativo, por lo tanto la solución debe de estar en la capa de presentación sin involucrar a otras partes de la aplicación como dominio, infraestructura, datos etc..

En un entorno imperativo, desde un sitio centralizado le iriamos diciendo a cada vista exactamente como se debe actualizar, por ejemplo desde el presenter si utilizamos model view presenter.

Sin embargo en un entorno declarativo basado en bindings no podemos decir cómo se actualiza un componente solo cuando se debe reconstruir de nuevo.

Levantando el estado

En un entorno declarativo los componentes son inmutables, es decir, se reconstruyen cuando hay algún cambio. Por lo tanto no es posible actualizar los componentes que dependen de un estado cuando este cambia, tenemos que reconstruirlos de nuevo.

Por este motivo la solución más simple, sin utilizar ninguna librería o patrón más complicado, es situar el estado en el componente superior más cercano a los componentes que comparten el estado.

En nuestro ejemplo siguiendo esta estrategía de levantamiendo de estado, la información del carrito de la compra la subiriamos a app:

shopping_cart_lifting_state_up

De esta forma cuando cambie el estado carrito de la compra, modificariamos el estado del componente app y se recontruiría todos los hijos, en este caso toda la app.

Ahora nos queda ver de que forma los componentes hijos tienen acceso a la información del carrito de la compra si esta ubicado en componentes superior jerárquicamente.

Perforación de propiedades

Al igual que el desarrollo orientado a objetos, donde si queremos que hijos de una jerarquía tengan acceso a información de un padre, esta puede ser pasada a los hijos via constructor o función.

Esta técnica de pasar la información de padres a hijos en los componentes de un entorno declarativo se conoce como perforación de propiedades o prop drilling.

En este tipo de desarrollo de interfaces suele existir dentro del componente un método render o build que se invoca cada vez que el estado cambia.

Desde este método render o build es donde indicamos como se debe renderizar el componente y a su vez indicamos los componentes hijos a utilizar. En este punto es donde se produce la comunicación de padres a hijos para abordar dos asuntos:

  • Paso de información de padres a hijos via constructor o función
  • Paso de funciones que manejan eventos de hijos a padres

De esta forma cuando se produce un cambio de estado en un padre y se reconstruye, por un lado le pasa información necesaria a sus hijos para el renderizado de estos, pero también las funciones que se van a encargar de escuchar cuando se producen eventos en los hijos que van afectar al estado que se encuentra en el padre superior.

shopping_cart_prop_drilling

Limitaciones

Esta técnica tiene algunas limitaciones:

  • A medida que una aplicación crece puede ser un problema utilizar esta técnica por toda la aplicación porque cada cambio de estructura en los componentes supone reajustar el paso de propiedades y funciones que manejan eventos de padres a hijos en la parte de la jerarquía afectada.
  • Además en nuestro caso, cuando añadimos un producto al carrito de la compra se reconstruye app y todos sus hijos, es decir casi toda la applicación. Se reconstruyen componentes de forma innecesaria como el listado de productos.

Sin embargo, creo que incluso en aplicaciones de tamaño considerable puede encajar bien esta técnica en ciertas partes de la aplicación sin recurrir a librerías o patrones más complejos.

Conclusiones

En este artículo hemos visto la forma más simple de gestión de estado en frameworks declarativos donde no se utiliza ninguna librería externa y de forma conceptual sin ver ejemplos de código.

Hemos aprendido la técnica, cuando puede ser útil y las limitaciones que tiene.

Como siempre depende del contexto que sea interesante utilizarla o sea mejor recurrir a utilizar otras librarías o patrones más complejos.

Para gestionar el estado viene bien recordar la expresión no matemos moscas a cañonazos, ya que utilizar patrones complejos por defecto para todo puede no ser la mejor opción.

Una de las mayores virtudes que podemos tener es saber identificar cuando una solución simple es mejor opción que una compleja y cuando una solución compleja es necesaría.

En futuros artículos veremos como se realiza esta técnica en algunas tecnologías como React.js o Flutter.