De observer manual a reactividad automática: el fin del cableado
El Observer clásico te obliga a declarar a mano qué depende de qué y a limpiar cada suscripción. Los signals borran ese trabajo con seguimiento automático de dependencias: leer un valor dentro de un cómputo lo suscribe solo, y el grafo se reconstruye y se limpia en cada ejecución. Esta lección cierra el nivel mostrando cómo el primitivo de 2026 automatiza la administración del átomo que abrió la serie.
Has recorrido el átomo (Observer), su versión mediada (pub/sub), su talón de Aquiles (la baja manual) y su eje profundo (push vs pull). Queda el salto que ordena todo lo anterior: pasar del cableado explícito de suscripciones a la reactividad automática. Los signals no inventan un mecanismo nuevo —siguen siendo sujeto y observador— sino que automatizan su administración: deducen las dependencias al leerlas y limpian el grafo solos. Este es el destino al que apuntaba el nivel entero.
- Ver el coste del cableado explícito de suscripciones en el Observer manual.
- Entender el seguimiento automático de dependencias mediante el observador actual.
- Comprender cómo el grafo se reconstruye y se limpia solo en cada ejecución.
- Situar el ecosistema de signals de 2026 y su estandarización en TC39.
El coste del cableado manual
Con el Observer clásico, las dependencias las declaras tú. Si un total depende de un precio y una cantidad, tienes que suscribir el recálculo a ambos a mano, y —esto es lo grave— recordar re-cablearlo cada vez que aparece un factor nuevo.
El cableado manual falla en las dos direcciones que ya conoces del nivel: si olvidas una suscripción, el valor queda obsoleto y la interfaz miente; si olvidas una baja, filtras memoria y ejecutas trabajo fantasma. Ambos fallos son silenciosos, y a ninguno lo atrapa el compilador.
// Manual: hay que suscribir a mano y acordarse de limpiar
function recalcular() {
total.set(precio.get() * cantidad.get());
}
precio.subscribe({ update: recalcular });
cantidad.subscribe({ update: recalcular });
// si mañana el total pasa a depender de un descuento,
// DEBES acordarte de suscribir recalcular también a ese tercer signal
El problema no es que sea difícil, es que es frágil y no compone: la correspondencia entre “de qué depende este cálculo” y “a qué está suscrito” la mantienes tú, y basta una distracción para que ambas se desincronicen sin que nada avise.
Y el coste crece con el tamaño. Diez valores derivados que dependen unos de otros forman una maraña de suscripciones que hay que tejer y destejer a mano en el orden correcto. Es la misma contabilidad de la lección anterior, ahora elevada a la enésima potencia por la composición: cada dependencia nueva multiplica los lugares donde puedes equivocarte, y la corrección entera descansa en tu memoria.
Este es el punto de dolor máximo del nivel, y por eso llega al final: has visto el átomo, lo has desacoplado con pub/sub, has sufrido su limpieza manual y has diseccionado su control del tiempo. Todo apuntaba a la misma pregunta incómoda —¿de verdad tengo que cablear yo cada dependencia, una por una?— y los signals son la respuesta.
El seguimiento automático de dependencias
Los signals eliminan ese cableado con una idea tan simple como potente: leer un signal dentro de un cómputo crea la dependencia automáticamente. No declaras nada; el mero acto de leer suscribe.
const precio = signal(10);
const cantidad = signal(2);
const total = computed(() => precio() * cantidad()); // deps detectadas al leer
effect(() => console.log("total", total())); // se re-ejecuta si total cambia
precio.set(12); // total se recalcula y el effect corre, sin cablear nada
Compara este código con la versión manual del principio: han desaparecido las llamadas a subscribe, la función recalcular con nombre propio y la obligación de re-cablear al añadir un factor. La dependencia entre total y sus signals no se declara en ninguna parte; se descubre en el instante en que computed ejecuta su función y lee precio y cantidad. Añadir un descuento sería tan simple como leerlo dentro del cálculo: la suscripción nacería sola.
El truco que lo hace posible cabe en un párrafo. Existe una variable global que apunta al observador actual —el cómputo o efecto que se está ejecutando ahora mismo—. Cuando un effect o un computed empieza a correr, se instala como observador actual. Cada vez que se lee un signal, este mira quién es el observador actual, se apunta a sí mismo en la lista de dependencias de ese observador y añade al observador a su propia lista de suscriptores. Al escribir en el signal, notifica a esos suscriptores. Alta y baja dejan de ser llamadas que tú escribes: son un efecto colateral de leer y de ejecutar.
En esqueleto, el mecanismo cabe en unas pocas líneas: un puntero global al observador en curso, una lectura que se registra, una ejecución que reinicia las dependencias.
let observadorActual = null;
function leer(sig) {
if (observadorActual) {
observadorActual.deps.add(sig); // el observador depende de este signal
sig.observadores.add(observadorActual); // y el signal lo recordará
}
return sig.valor;
}
function ejecutar(observador) {
observador.deps.clear(); // olvida las dependencias del turno anterior
observadorActual = observador;
try { observador.fn(); } // al correr, cada lectura vuelve a registrarse
finally { observadorActual = null; }
}
Ese deps.clear de cada ejecución es la clave de la limpieza automática: como las dependencias se reconstruyen desde cero en cada corrida, las que dejan de leerse simplemente no vuelven a registrarse. No hay que darlas de baja porque nunca sobreviven a un turno en que no se usan.
Una nota práctica sobre sintaxis: la forma de leer y escribir un signal varía entre implementaciones —signal() y signal.set() en Angular y Solid, signal.value en Preact y en la propuesta de TC39, ref.value en Vue—, pero el mecanismo por debajo es idéntico en todas: leer registra la dependencia, escribir la invalida. Aprendes el concepto una vez y solo cambias de sintaxis al saltar de un framework a otro.
flowchart TD A[signal precio] -->|leido dentro de| C[computed total] B[signal cantidad] -->|leido dentro de| C C -->|leido dentro de| E[effect pinta la UI] style A fill:#89b4fa,color:#11111b style B fill:#89b4fa,color:#11111b style C fill:#cba6f7,color:#11111b style E fill:#a6e3a1,color:#11111b
El grafo se reconstruye y se limpia solo
Como las dependencias se recolectan en cada ejecución, el grafo es siempre exacto. Si un cómputo tiene una rama condicional que este turno no lee cierto signal, deja de depender de él automáticamente —las dependencias son dinámicas, no una lista fija—. Y al destruir un efecto, todas sus suscripciones se sueltan de golpe, sin que tú recorras ninguna lista de bajas.
Fíjate en cuántos problemas del nivel se disuelven de una vez. La baja manual y el lapsed listener de la lección anterior desaparecen: no hay unsubscribe que olvidar porque el sistema lo hace por ti al re-rastrear o al destruir el efecto. El orden de limpieza deja de ser tu responsabilidad. Y el diseño es push-pull, como vimos: la escritura empuja una invalidación barata por el grafo y la lectura tira del valor recalculándolo perezosamente, sin cómputo inútil ni glitches. Los cuatro dolores del nivel —memoria, orden, cableado y control del tiempo— quedan resueltos por el mismo primitivo.
computed perezoso, effect ansioso
Una sutileza que conviene fijar: un computed es perezoso —no recalcula hasta que alguien lo lee— mientras que un effect es ansioso —corre en cuanto una de sus dependencias cambia, porque su razón de ser es producir un efecto en el mundo, como pintar la pantalla—. Ambos usan el mismo autoseguimiento y se diferencian solo en cuándo se ejecutan: un computed que nadie lee jamás se calcula, mientras que un effect corre siempre que su grafo se ensucia. Esa asimetría es push-pull otra vez, mirado desde arriba: el effect es el punto exacto donde el pull perezoso del grafo interior se convierte en el push ansioso hacia la interfaz.
Otra pieza que el autoseguimiento habilita es el batching: si escribes en varios signals seguidos, el sistema puede agrupar las invalidaciones y recalcular los efectos una sola vez al final, en lugar de una por escritura. Cambiar el precio y la cantidad en la misma operación no dispara dos repintados, sino uno. Es la respuesta automática al problema de la reentrada y las cascadas que vimos en el Observer crudo: donde antes encadenabas notificaciones a mano y arriesgabas estados intermedios, ahora el runtime fusiona los cambios en una transacción coherente.
La regla de oro: preferir computed a effect
Un consejo que atraviesa todo el ecosistema de 2026: la mayoría de lo que crees que es un effect es en realidad un computed. Si un valor se deriva de otros, decláralo computed y reserva el effect para los verdaderos efectos de frontera —pintar, registrar, sincronizar con algo externo—. Abusar de effect para copiar un signal en otro reintroduce a mano el cableado que los signals venían a eliminar, y con él vuelven los bugs de sincronización. El grafo debe ser sobre todo declarativo: los efectos son sus hojas, no su tejido.
El ecosistema de 2026
Lo notable de los signals es que la industria ha convergido en la forma del primitivo. Ya no es la apuesta de una librería, sino un consenso de diseño que se está estandarizando.
TC39 Signals
La propuesta de signals para el lenguaje avanza en el comité, con un polyfill de referencia. Su meta es un primitivo común para que los frameworks compartan el mismo grafo reactivo en vez de reimplementarlo cada uno.
Solid y Preact
Reactividad de grano fino llevada al extremo: el grafo actualiza el DOM exacto que cambió, sin árbol virtual que diferenciar. El seguimiento automático es el corazón del rendimiento.
Angular y Vue
Angular adoptó los signals como base de su reactividad; Vue lleva años con ref, computed y watchEffect sobre la misma idea. Dos frameworks mayores, el mismo primitivo.
Svelte 5 runes
Las runes $state, $derived y $effect son signals con azúcar de compilador. Hasta un framework que compila a JavaScript plano abraza el modelo de autoseguimiento.
Que TC39, Solid, Angular, Vue, Preact y Svelte compartan el mismo modelo mental no es casualidad: es la señal de que el problema de la propagación de estado en el cliente ha encontrado, por fin, una respuesta canónica.
La convergencia tiene una consecuencia práctica enorme. Si el primitivo se estandariza en el propio motor de JavaScript, dejará de ser responsabilidad de cada framework reimplementarlo, y librerías hoy incompatibles podrán compartir un mismo grafo reactivo por debajo. Aprender signals en 2026 no es apostar por una herramienta concreta: es aprender el vocabulario común hacia el que se dirige toda la plataforma.
El salto de esta lección es el mismo que separa la programación imperativa de la declarativa, aplicado a la reactividad. Con el Observer manual le dices a la máquina qué hacer: cuando cambie A, suscribe esto, ejecuta aquello, y no olvides darte de baja. Con signals describes qué es verdad: el total es el precio por la cantidad, y punto. La relación de dependencia deja de ser una instrucción que ejecutas y se convierte en un hecho que el runtime lee de tu propio código, con solo observar qué lees mientras calculas. Esa inversión lo cambia todo, porque elimina la clase de error más traicionera de la reactividad: la divergencia entre lo que un cálculo de verdad usa y aquello a lo que está suscrito. En el Observer clásico esas dos cosas las mantenías tú sincronizadas a pulso, y toda la fragilidad del nivel —fugas, valores obsoletos, órdenes de limpieza— nacía de que se te desincronizaran. Los signals las funden en una sola: aquello que lees es, por definición, aquello de lo que dependes, y no puede haber discrepancia porque son el mismo acto. Vistas así, las cinco lecciones del nivel cuentan una sola historia. El Observer nos dio el átomo. El pub/sub lo desacopló. Las suscripciones manuales revelaron el precio de administrarlo a mano. El eje push vs pull nos dio el vocabulario para hablar del control del tiempo. Y los signals cierran el círculo automatizando la administración del átomo: el mismo sujeto y el mismo observador de la primera lección, pero con el cableado y la limpieza delegados a la máquina, igual que un día delegamos la memoria al recolector de basura. No has aprendido cinco temas; has aprendido cómo un primitivo madura hasta desaparecer de tu vista. Ese es el nivel dios de los primitivos de reactividad: entenderlos tan a fondo que dejas de tener que pensarlos.
- Escribe la versión manual del ejemplo: un total suscrito a mano a un precio y una cantidad, con su recálculo y su baja explícita.
- Añade un tercer factor y comprueba en carne propia que debes acordarte de suscribirlo, o el total queda obsoleto.
- Reescríbelo con
signal,computedyeffectusando el polyfill de la propuesta de TC39, y verifica que el tercer factor se integra solo con solo leerlo. - Demuestra el rastreo dinámico: mete una rama condicional en el
computedy confirma que la dependencia aparece y desaparece según qué signal se lea en cada ejecución.