Qué es un signal
Un signal es el átomo de la reactividad fina: un valor envuelto en un getter y un setter donde leer registra una dependencia y escribir notifica a quien dependía. El mecanismo que en 2026 sostiene Solid, Angular, Preact, Vue y Svelte 5.
Un signal aparenta ser una caja donde guardar un valor, pero su verdadero oficio no es almacenar: es observar. Lo que lo separa de una variable corriente es que el acto de leerlo deja rastro —queda anotado quién lo consultó— y el acto de escribirlo dispara un aviso a todos los que dependían de él. Ese doble gesto, leer-registra y escribir-notifica, es el átomo indivisible sobre el que se levanta toda la reactividad fina moderna. Comprende este mecanismo aquí y habrás comprendido de golpe a Solid, Angular, Preact, Vue y Svelte 5, porque los cinco no son más que dialectos de esta misma idea.
- Definir un signal como un valor reactivo expuesto por un getter y un setter.
- Comprender que leerlo crea una dependencia y escribirlo notifica.
- Distinguir un signal de una variable ordinaria y de un event emitter.
- Situar el ciclo leer-escribir-notificar como cimiento de todo lo que sigue.
El átomo de la reactividad
Un signal es un contenedor de un valor expuesto mediante dos operaciones: un getter que devuelve el valor vigente y un setter que lo reemplaza. Escrito en su forma más desnuda, sin framework alguno, cabe en unas pocas líneas y no esconde ninguna magia:
type Efecto = () => void;
let activo: Efecto | null = null; // el lector en curso, si lo hay
function signal<T>(inicial: T) {
let valor = inicial;
const dependientes = new Set<Efecto>();
const leer = (): T => {
if (activo) dependientes.add(activo); // anota a quien me esta leyendo
return valor;
};
const escribir = (siguiente: T): void => {
if (Object.is(valor, siguiente)) return; // sin cambio real, sin ruido
valor = siguiente;
for (const efecto of dependientes) efecto(); // avisa a todos
};
return [leer, escribir] as const;
}
Fíjate en que el valor en sí es lo menos interesante del código. Lo que le da poder son las dos líneas que rodean al return valor: el conjunto dependientes y el bucle que los invoca. Un signal es, con rigor, un valor más la lista viva de quienes deben enterarse cuando ese valor cambie. Y esa lista no la mantienes tú a mano: la teje el propio acto de leer.
La forma en pares que devolvimos —un getter y un setter separados— es solo una de las convenciones posibles. Otros sistemas empaquetan las dos operaciones en un mismo objeto y exponen lectura y escritura tras una propiedad .value, con un getter y un setter de propiedad:
function signalObjeto<T>(inicial: T) {
let valor = inicial;
const dependientes = new Set<Efecto>();
return {
get value(): T {
if (activo) dependientes.add(activo); // leer registra
return valor;
},
set value(siguiente: T) {
if (Object.is(valor, siguiente)) return;
valor = siguiente;
for (const efecto of dependientes) efecto(); // escribir notifica
},
};
}
El par y el objeto son la misma idea con distinta fachada: en ambos, leer registra y escribir notifica. La lección 5 mostrará que esa elección de fachada —paréntesis, .value o magia de compilador— es lo único que de verdad separa a los cinco frameworks de 2026.
Leer suscribe, escribir publica
La asimetría entre las dos operaciones es el corazón del modelo. Leer no es un acto neutro: cuando un efecto activo llama al getter, el signal lo captura y lo añade a su conjunto de dependientes. Es una suscripción implícita, sin que nadie escriba la palabra suscribir. Escribir, en cambio, es un acto de publicación: el setter compara el valor nuevo con el viejo mediante Object.is y, solo si de verdad cambió, recorre a sus dependientes y los vuelve a ejecutar.
const [contar, ponerContar] = signal(0);
activo = () => console.log("el conteo vale", contar()); // este efecto lee
contar(); // al leer aqui, el efecto queda registrado
activo = null;
ponerContar(1); // escribir dispara al efecto: imprime "el conteo vale 1"
ponerContar(1); // mismo valor: Object.is lo corta, no imprime nada
Esa comparación de igualdad no es un adorno: es lo que impide que el grafo reactivo se sacuda ante escrituras que no cambian nada. Un signal bien construido es perezoso en la notificación —guarda silencio salvo cuando el valor realmente se mueve—, y esa propiedad, multiplicada por miles de nodos, es la diferencia entre una interfaz que recalcula lo justo y otra que se recalcula entera sin motivo.
flowchart LR R[un efecto lee el signal] -->|registra dependencia| S[el signal recuerda al efecto] W[alguien escribe el signal] --> D[compara con Object.is] D -->|cambio real| N[notifica a los dependientes] D -->|mismo valor| X[silencio total] N --> R style S fill:#89b4fa,color:#11111b style N fill:#a6e3a1,color:#11111b style X fill:#f38ba8,color:#11111b
No confundas un signal con un event emitter. Un evento transporta ocurrencias —se hizo clic— y no recuerda nada entre disparos; un signal transporta un valor vigente —el conteo vale 3— que siempre puedes volver a leer. El evento es un pulso instantáneo; el signal es un nivel persistente. Por eso un suscriptor que llega tarde a un evento se pierde lo ya emitido, mientras que quien lee un signal obtiene siempre el estado actual, sin importar cuándo se asomó.
Un signal no es una variable
Una variable ordinaria responde a una sola pregunta: cuánto vale ahora. Un signal responde a esa y a una segunda que la variable jamás sabrá contestar: quién debe reaccionar cuando cambie. Esa segunda dimensión —la relación viva entre el dato y sus consumidores— es exactamente lo que un signal materializa y una variable descarta. Cuando escribes x = x + 1 sobre una variable, nadie se entera; cuando lo haces sobre un signal, todo el que dependía de él se recalcula.
Leer registra
Llamar al getter dentro de un contexto reactivo suscribe a ese lector: el signal lo anota en su conjunto de dependientes sin que nadie lo pida de forma explícita.
Escribir notifica
Llamar al setter con un valor distinto recorre a todos los dependientes y los vuelve a ejecutar. Es una publicación dirigida a quien estuviera escuchando.
La igualdad filtra
Antes de notificar, el setter compara con Object.is. Si el valor no cambió, no hay aviso: el grafo permanece quieto ante escrituras estériles.
El Object.is del setter esconde una decisión de diseño con consecuencias. Con igualdad por referencia, reasignar un objeto de idéntico contenido pero distinta identidad cuenta como cambio y notifica; mutar ese objeto en su sitio, en cambio, no altera la referencia y no notifica nada. Por eso los signals conviven tan bien con datos inmutables: reemplazar en lugar de mutar es lo que hace visible el cambio. Muchos sistemas permiten además inyectar tu propia función de igualdad para afinar cuándo un valor cuenta como nuevo.
Grano fino: la precisión que lo cambia todo
El adjetivo que acompaña siempre a los signals es fino, y no es casual. Cuando la unidad de reactividad es el valor individual —no el componente entero, no el árbol completo—, el sistema puede propagar un cambio exactamente hasta donde importa y ni un paso más. Cambiar un nombre actualiza solo el trozo de pantalla que lee ese nombre, sin volver a evaluar el resto. Esta granularidad es la razón por la que el modelo resurgió con fuerza: promete el rendimiento de la manipulación quirúrgica del DOM con la ergonomía de la programación declarativa.
Conviene fijar el vocabulario que usaremos en todo el nivel. El signal escribible que acabas de ver es la fuente: el único punto donde el estado entra al grafo por una escritura tuya. A partir de él, la lección 3 construirá derivaciones —valores que se calculan de otros— y efectos —reacciones que corren cuando algo cambia—. Fuente, derivación y reacción son las tres piezas, y las tres se apoyan en el mismo par leer-registra, escribir-notifica que este signal ya encierra completo.
La idea tiene raíces hondas: el observable de Knockout hacia 2010, las computaciones de S.js, el modelo transparente de MobX. Lo que cambió en esta ola no es el concepto sino el consenso —el mismo diseño fino adoptado casi a la vez por todo el ecosistema— y la inminente estandarización en TC39. No asistes a una moda pasajera, sino a la maduración de un patrón con más de quince años de historia.
La revolución silenciosa del signal es topológica: invierte la dirección del conocimiento en el grafo de dependencias. En el patrón observer clásico, el sujeto mantiene una lista explícita de observadores y eres tú quien, a mano, llama a suscribir y a desuscribir; el que emite está obligado a conocer a todos los que escuchan, y cada suscripción que abres es una que debes recordar cerrar. En el modelo de signals, el dependiente no se declara jamás: simplemente lee el signal dentro de un contexto reactivo, y el sistema deduce la arista del grafo por el mero hecho de esa lectura. El productor no sabe quién lo consume; lo descubre en el instante mismo en que es leído, y lo olvida cuando deja de serlo. Esta inversión es lo que hace la reactividad fina tan ergonómica y tan segura: no hay libro de contabilidad de suscripciones que mantener, no hay fugas de memoria por olvidar un desuscribir, porque la dependencia nace y muere con la lectura. Cuando en las próximas lecciones veas el rastreo automático, los valores computados, el Watcher del proposal de TC39 y los cinco dialectos de los frameworks, reconocerás que todos son elaboraciones de este único gesto: un valor que, al ser leído, teje en silencio la arista que lo une a quien lo necesita, y que al ser escrito la recorre para avisar. Todo lo demás —caché, pereza, scheduling, sintaxis— es ingeniería alrededor de esta semilla. Quien la interioriza deja de ver cinco frameworks distintos y empieza a ver una sola idea vestida de cinco maneras.
- Copia la función
signalde arriba, crea un contador y léelo dentro de un efecto; comprueba que reacciona al escribir. - Añade un
console.logdentro del setter y verifica queObject.iscorta en seco las escrituras que no cambian el valor. - Registra dos efectos distintos que lean el mismo signal y confirma que una sola escritura dispara a ambos.
- Guarda un objeto en un signal, mútalo en su sitio y observa que no notifica; reemplázalo por uno nuevo y observa que sí.
- Explica en una frase por qué el productor no necesita conocer de antemano a sus consumidores.