Señales Vanilla

Aunque parezca mentira, hay vida más allá de los frameworks y librerías pesadas que manejamos hoy en día para cualquier web.

Todos nos emocionamos mucho cuando conocimos React.js y vimos que podíamos quitarnos todo el spaghetti de jQuery y tener aplicaciones dinámicas expresadas de manera declarativa.

Sin embargo, el tiempo nos ha llevado a apartar el estándar web, construyendo aplicaciones estáticas o aplicaciones con poca interacción de una manera estúpida. Con cientos de librerías y dependencias que fastidian la experiencia, tanto al usuario, como al desarrollador.

En mi pueblo, a esto se le ha llamado toda la vida "matar moscas a cañonazos".

Alternativas #

Siempre hay alternativas, y hoy en concreto, quería atajar una. Reactividad nativa. En otro momento atacaremos la interfaz declarativa.

Inicialmente, React utilizaba unas propiedades de clase para guardar el estado. Internamente, añadía observadores a esas propiedades, para volver a renderizar la interfaz si fuese necesario.

Durante los últimos años, sin embargo, nos han enseñado que la "única" manera de lograr reactividad era mediante un "hook" (una función, básicamente, lo llamen como lo llamen) que se encargaba de actualizar el estado interno de la aplicación. Sirviendo magia.

Pero React no es el único contendiente del mercado, y Preact (una versión light de React), junto con otros frameworks han estandarizado una nueva manera. Señales.

Señales Vanilla #

Las señales son sencillas de implementar sin necesidad de librerías externas. En esencia, son objetos que almacenan un valor y notifican a los observadores cuando ese valor cambia.

¿Cuáles son los requisitos?

  • Persistir el estado de la aplicación.
  • Leer y actualizar el estado.
  • Permitir la suscripción de observadores.
  • Notificar a los observadores cuando el estado cambia.
  • Estados computados a partir de otros.

No es tan difícil, ¿no?

Estado #

Vamos a implementar un objeto capaz de guardar el estado y poder leerlo y actualizarlo.

const señal = (_interno) => {
  return {
    get valor() {
      return _interno;
    },
    set valor(nuevo) {
      if (nuevo === _interno) return;

      _interno = nuevo;
    },
  };
};
          

Ok, hecho. Ya podemos utilizarlo tal que así:

const contador = señal(0);
console.log(contador); // Log: 0

contador.valor += 1;
console.log(contador); // Log: 1
          

Reactividad #

Pero sin reactividad no tiene mucha gracia. Vamos a añadírsela.

let suscriptor = null;

const señal = (_interno) => {
  const suscriptores = new Set();

  return {
    get valor() {
      if (suscriptor) suscriptores.add(suscriptor);
      return _interno;
    },
    set valor(nuevo) {
      if (nuevo === _interno) return;

      _interno = nuevo;
      suscriptores.forEach((fn) => fn());
    },
  };
};

const efecto = (fn) => {
  suscriptor = fn;
  fn();
  suscriptor = null;
};
          

Así de simple. Podemos suscribirnos a la señal con "efecto" y pasarle una función de callback. Y cada vez que cambie la señal se ejecutará la función.

const contador = señal(0);
efecto(() => {
  console.log(`El contador ha cambiado a ${contador}`);
}); // Log: El contador ha cambiado a 0

contador.valor += 1; // Log: El contador ha cambiado a 1
contador.valor += 1; // Log: El contador ha cambiado a 2
          

De igual manera, podemos actualizar el valor al pulsar un botón en lugar de por código y que esto actualice un elemento del DOM en lugar de pintar por consola.

Estado derivado #

Podemos crear estados derivados a partir de otros estados. Por ejemplo, un estado que reúna el valor de dos señales.

const derivado = (fn) => {
  const _señal = señal();

  efecto(() => (_señal.valor = fn()));

  return _señal;
};
          

De esta manera, podemos crear un estado que se actualice automáticamente cuando cambien las señales de las que depende. O simplemente un efecto conjunto que reaccione a múltiples señales.

const nombre = señal("Pau");
const apellido = señal("Cubarsí");

const nombreCompleto = derivado(() => `${nombre} ${apellido}`);

efecto(() => {
  console.log("Ya está aquí " + nombreCompleto)
}) // Log: Ya está aquí Pau Cubarsí

apellido.valor = "Gasol"; // Log: Ya está aquí Pau Gasol
nombre.valor = "Marc"; // Log: Ya está aquí Marc Gasol
          

Conclusión #

Y ya está. Hemos implementado un sistema de señales en Vanilla Javascript sin librerías externas.

No hemos utilizado 500Mb de Javascript por inercia, hemos pensado y hemos utilizado la plataforma, que tiene mucho que ofrecer.

El código completo estará disponible en los retales