English versión: https://medium.com/@ger86/react-hooks-and-the-observer-pattern-1e4274f0e5f5

La llegada de los React Hooks ha supuesto multitud de cambios en la forma en que diseñamos y programamos aplicaciones con React:

  • han permitido añadir estado interno a los componentes funcionales, de modo que éstos han comenzado a sustituir a los componentes "clásicos" ;
  • han fomentado el uso de un estilo declarativo de programación debido a su carácter funcional;
  • y han simplificado la forma en que podemos reutilizar la lógica a lo largo de toda la aplicación, algo que empleando "Class componentes" era algo más complicado.

Es en torno a este último punto sobre el que gira este artículo: la forma en que podemos emplear los hooks para implementar algunos de los patrones de diseño más conocidos, de modo que podamos construir aplicaciones más fáciles de mantener y de escalar.

¡Vamos a ver cómo!

Introducción

Si has aterrizado en este artículo sin saber qué es un patrón de diseño trataré de resumirlo brevemente aunque te recomiendo que acudas a cualquiera de los siguientes enlaces para profundizar sobre este concepto tan importante en el desarrollo de aplicaciones:

Un patrón de diseño es una solución general y reusable a un problema común dentro de un contexto determinado en el diseño de una aplicación.

Es decir, los patrones de diseño aportan soluciones a determinados problemas que aparecen de forma recurrente al plantear la arquitectura de una aplicación de modo evitándonos tener que reinventar la rueda constantemente.

Su potencia radica en que su uso y aplicación no depende del lenguaje en el que estemos desarrollando sino que, al abstraer la solución por medio de diagramas de clase, podremos implementarlos en cualquier aplicación software.

Otra de las ventajas de los patrones de diseño es que aportan un lenguaje común a la hora de comunicarse con otros desarrolladores ya que cada uno de ellos define un nombre específico para referirse a él así como los elementos que participan en la solución que propone. Esto permite que, independientemente del tipo de aplicación desarrollada o el lenguaje usado, se pueda emplear un mismo "diccionario" para referirse a determinados conceptos. De este modo, el código se "autodocumenta" permitiendo a las futuras personas involucradas en su mantenimiento identificar rápidamente la solución implementada.

React Hooks y el patrón "Observer"

En el patrón "Observer" un objeto, llamado "subject", mantiene una lista de sus dependencias, llamadas "observers", y las notifica automáticamente de cualquier cambio de estado, generalmente llamando a uno de sus métodos.

Gracias a este patrón podemos tener aplicaciones más desacopladas (pues los observers no necesitan acoplarse al subject) y mantenibles ya que cada Observer es independiente de los demás, de modo que un cambio en la funcionalidad de uno de ellos no implica la modificación del resto.

Su esquema UML de clases y secuencia son los siguientes:

None
By Vanderjoe — Own work, CC BY-SA 4.0, https://commons.wikimedia.org/w/index.php?curid=61809260

Ahora imaginad que queremos desarrollar una aplicación en React en donde los usuarios pueden visualizar el estado del tiempo en una ciudad en diferentes sitos de la aplicación, por ejemplo, dentro de una barra lateral con sus ciudades favoritos y en un listado de ciudades.

Por tanto, tendríamos dos componentes:

  • FavouriteCityComponent , para mostrar la temperatura de una ciudad dentro de la sidebar de nuestra aplicación.
  • CityTeaserComponent , para mostrar la temperatura de una lista de ciudades.

Lo conveniente será que el estado del clima en una ciudad se encuentre centralizado y que ambos componentes lo reciban, es decir, "liberarles" de ser ellos quienes tengan que solicitarlo (lo que terminaría por provocar código duplicado). ¿Y cómo podemos conseguir centralizar el estado del clima en una ciudad de modo que ambos componentes se actualicen cada vez que se produce un cambio? Gracias al patrón "observer":

None

Atendiendo al diagrama:

  • Weather que actúa como Subject del patrón "observer" centralizando el estado del tiempo en una ciudad.
  • FavouriteCity y CityTeaser actúan como Observers recibiendo por medio de su método onWeatherChanged la actualización del tiempo procedente de la clase Weather . Estos "observers" pueden solicitar ser notificados ( notify ) por medio del método attach del "subject".

Teniendo claro esto, primero implementaremos el código de la clase Weather . De cara poder definir nuevos tipos e interfaces así como poder "tipar " los argumentos de cada método emplearé Typescript como lenguaje aunque si queréis podéis escribir vuestra propia versión en Javascript sin ningún tipo de problema.

Clase Weather

Según el diagrama, la clase WeatherSubject deberá contar con los siguientes métodos:

  • attach , de cara a que un "observer" pueda registrarse y recibir notificaciones cuando se actualice la temperatura.
  • detach , para permitir a los "observers" darse de baja cuando ya no deseen recibir más notificaciones de estado (y así también prevenir memory leaks provocados por mantener referencias en memoria a objetos que ya no se están usando).
  • notify , método que notificará a los "observers" registrados cada vez que se produzca una actualización en el clima.
None
  • En la línea 1 estamos definiendo el tipo de los "observers" que podrán suscribirse a la clase Weather. Para nuestro caso será una función que actuará a modo de callback recibiendo como argumento la temperatura obtenida.
  • En las líneas 7 y 11 definimos el cuerpo de los método attach y detach de modo que los "observers" puedan suscribirse y desuscribirse a actualizaciones de estado.
  • En la línea 15 definimos el método updateWeather que obtiene el clima cada 1 segundo y notifica a los "observers" por medio del método notify .
  • En la línea 33 el método notify recorre el array de "observers" y los invoca con la temperatura recibida.

Componente "FavouriteCity"

Nuestro componente FavouriteCity actuará como "observer" de modo que:

  • Al montarse deberá suscribirse como observer en la clase Weather por medio del método attach .
  • Al desmontarse deberá "desuscribirse" de la clase Weather por medio del método dettach y así prevenir que sea invocado cuando ya no está siendo empleado.

Es aquí donde entran en acción los React Hooks. Crearemos un componente funcional que empleará dos hooks:

  • useState para almacenar la temperatura actual. Además, este hook nos proporcionará una función para actualizar la temperatura actual que emplearemos dentro del callback que registraremos en el subject WeatherSubject .
  • useEffect para suscribirse y desuscribirse del subject WeatherSubject.
None
  • En la línea 6 definimos un estado para el componente FavouriteCity que almacenará la temperatura actual y nos permitirá actualizarla mediante el método setCurrentTemperature .
  • En la línea 8 definimos el callback que registraremos en WeatherSubject : una función que recibe como argumento un Number con la temperatura obtenida y que empleará el método setCurrentTemperature para actualizar el estado del hook useState .
  • En la línea 12 mediante el hook useEffect nos suscribimos una vez (empleando como segundo argumento del hook un array vacío [] ) a WeatherSubject por medio de su método attach y en el momento en que el componente se desmonte nos desuscribimos mediante el método detach .
  • Finalmente en la línea 19 pintamos la temperatura que tenemos almacenada en el hook de estado useState .

Componente "CityTeaser"

Como habéis podido ver en el componente anterior, el patrón "Observer" nos ha permitido desacoplar la actualización del clima del componente FavouriteCity. Además, gracias al hook useState en apenas 20 líneas hemos resuelto el problema gracias al callback que nos proporciona para actualizar el estado.

De cara a que podáis ver cómo el hook useReducer también se integra perfectamente con el patrón "Observer" implementaré el componente CityTeaser de esa forma:

None

En este caso hemos requerido algo más de código:

  • En las líneas 4 y 6 definimos la que será nuestra función reducer : recibirá como primer argumento el estado actual y como segundo argumento la acción que se ha lanzado representada por un objeto con la propiedad type y la propiedad temperature con el valor de la temperatura a actualizar.
  • En la línea 14 hacemos uso del hook useEffect para obtener la función dispatch y a continuación definimos la función onTemperatureUpdated para hacer un dispatch de la temperatura recibida cada vez que WeatherSubject nos notifique.
  • Y finalmente, al igual que en la clase FavouriteCity , empleamos el hook useEffect para suscribirnos y desuscribirnos cuando el componente se monte y desmonte respectivamente.

Conclusiones

El patrón "Observer" es probablemente uno de los patrones más extendidos en el desarrollo de aplicaciones debido a las ventajas que proporciona:

  • Bajo acoplamiento entre los componentes que forman la aplicación.
  • Un único sentido para la transmisión de información y estado, es decir, es el Subject quien se encarga de controlar el estado de una determinada parte de la aplicación y de notificar a los "observers" que lo han solicitado.

Y gracias a los React Hooks es muy sencillo implementarlo en nuestras aplicaciones tal y como he tratado de mostrar en este artículo.

Espero que este artículo os haya servido para descubrir otra forma de usar los React Hooks ya que han venido para quedarse.

¿Quieres recibir más artículos como este?

Si te ha gustado este artículo te animo a que te suscribas a la newsletter que envío cada domingo con publicaciones similares a esta y más contenido recomendado: 👇👇👇