Signals en JavaScript: El Futuro de la Reactividad Que Puede Cambiar la Web
Hola HaWkers, si trabajas con frameworks JavaScript modernos, probablemente ya escuchaste hablar de Signals. Lo que comenzo como un enfoque alternativo para la gestion de estado se esta convirtiendo en un consenso entre Angular, Vue, Solid, Svelte y potencialmente entrando en la especificacion oficial de JavaScript.
Vamos a entender que son Signals, por que estan conquistando la comunidad y como pueden cambiar la forma en que escribimos aplicaciones web.
Que Son Signals
Concepto Fundamental
Signals son primitivas reactivas que almacenan un valor y notifican automaticamente a sus dependientes cuando ese valor cambia. A diferencia del modelo tradicional de re-renderizado completo, Signals permiten actualizaciones quirurgicas solo donde es necesario.
Anatomia basica de un Signal:
// Creando un signal
const count = signal(0)
// Leyendo el valor
console.log(count()) // 0
// Actualizando el valor
count.set(1)
// o
count.update(n => n + 1)
// Derivando valores (computed)
const doubled = computed(() => count() * 2)
// Reaccionando a cambios (effect)
effect(() => {
console.log(`Count is: ${count()}`)
})Por Que Signals Son Diferentes
La magia de Signals esta en la reactividad fina (fine-grained reactivity). Compara con enfoques tradicionales:
React (re-render completo):
function Counter() {
const [count, setCount] = useState(0)
const [name, setName] = useState('User')
// Cuando count cambia, TODO el componente re-renderiza
// Incluyendo partes que solo dependen de name
return (
<div>
<h1>Hello, {name}</h1>
<p>Count: {count}</p>
<button onClick={() => setCount(c => c + 1)}>+</button>
</div>
)
}Con Signals (update quirurgico):
function Counter() {
const count = signal(0)
const name = signal('User')
// Cuando count cambia, SOLO el texto del count actualiza
// El h1 con name ni se toca
return (
<div>
<h1>Hello, {name()}</h1>
<p>Count: {count()}</p>
<button onClick={() => count.update(c => c + 1)}>+</button>
</div>
)
}
La Convergencia de los Frameworks
Quien Esta Usando Signals
En 2026, practicamente todos los frameworks JavaScript han adoptado o estan adoptando Signals.
Estado por framework:
| Framework | Estado Signals | Desde |
|---|---|---|
| Solid.js | Nativo (pionero) | 2021 |
| Angular | Signals API | 2023 |
| Vue 3.6 | Alien Signals | 2026 |
| Svelte 5 | Runes ($state) | 2024 |
| Preact | @preact/signals | 2022 |
| Qwik | Signals nativos | 2022 |
| React | En discusion | - |
La Propuesta TC39
Lo mas interesante es que Signals pueden convertirse en parte del JavaScript nativo. Hay una propuesta activa en TC39 para agregar Signals a la especificacion.
Estado de la propuesta:
TC39 Proposal: Signals
Stage: 1 (Proposal)
Champions: Daniel Ehrenberg, Rob Riggs
Objetivo: Crear una primitiva de reactividad estandarizada
que funcione como base para todos los frameworksPropuesta de API nativa:
// Posible API nativa (especulativa)
const count = Signal.state(0)
const doubled = Signal.computed(() => count.get() * 2)
Signal.effect(() => {
console.log(doubled.get())
})
count.set(5) // Logs: 10
Como Funcionan los Signals Por Dentro
El Sistema de Dependencias
Signals usan un grafo de dependencias que se construye automaticamente durante la ejecucion.
// Internamente, algo asi sucede:
class Signal {
#value
#subscribers = new Set()
constructor(initialValue) {
this.#value = initialValue
}
get() {
// Si estamos dentro de un effect/computed,
// registra esta dependencia
if (currentTracking) {
this.#subscribers.add(currentTracking)
}
return this.#value
}
set(newValue) {
if (this.#value !== newValue) {
this.#value = newValue
// Notifica a todos los subscribers
this.#subscribers.forEach(sub => sub.notify())
}
}
}Tracking Automatico
El tracking de dependencias sucede automaticamente cuando lees un signal dentro de un contexto reactivo.
const firstName = signal('John')
const lastName = signal('Doe')
const age = signal(30)
// Este computed depende SOLO de firstName y lastName
// age no es una dependencia porque no fue leido
const fullName = computed(() => {
return `${firstName()} ${lastName()}`
})
// Cambiar age NO recalcula fullName
age.set(31) // fullName no reacciona
// Cambiar firstName recalcula fullName
firstName.set('Jane') // fullName recalculaPush vs Pull
Signals usan un modelo hibrido push-pull:
// PUSH: Cuando un signal cambia, "empuja" notificacion
// a sus dependientes directos
const a = signal(1)
const b = computed(() => a() * 2) // b depende de a
const c = computed(() => b() + 10) // c depende de b
a.set(2) // Push: a notifica b, b notifica c
// PULL: Valores solo se recalculan cuando se leen
// (evaluacion perezosa)
console.log(c()) // Pull: c calcula b, b calcula a
Implementaciones Practicas
Signals en Angular
Angular introdujo Signals como alternativa a RxJS para casos simples.
import { signal, computed, effect } from '@angular/core'
@Component({
selector: 'app-counter',
template: `
<h1>Count: {{ count() }}</h1>
<h2>Doubled: {{ doubled() }}</h2>
<button (click)="increment()">+</button>
`
})
export class CounterComponent {
count = signal(0)
doubled = computed(() => this.count() * 2)
constructor() {
effect(() => {
console.log(`Count changed to: ${this.count()}`)
})
}
increment() {
this.count.update(n => n + 1)
}
}Signals en Solid.js
Solid fue el pionero en Signals modernos.
import { createSignal, createEffect, createMemo } from 'solid-js'
function Counter() {
const [count, setCount] = createSignal(0)
const doubled = createMemo(() => count() * 2)
createEffect(() => {
console.log(`Count is ${count()}`)
})
return (
<div>
<p>Count: {count()}</p>
<p>Doubled: {doubled()}</p>
<button onClick={() => setCount(c => c + 1)}>+</button>
</div>
)
}Signals en Vue 3.6
Vue esta introduciendo Signals a traves del Vapor Mode.
<script setup>
import { signal, computed, effect } from 'vue/vapor'
const count = signal(0)
const doubled = computed(() => count() * 2)
effect(() => {
console.log(`Count: ${count()}`)
})
function increment() {
count.set(count() + 1)
}
</script>
<template>
<div>
<p>Count: {{ count() }}</p>
<p>Doubled: {{ doubled() }}</p>
<button @click="increment">+</button>
</div>
</template>
Patrones Avanzados con Signals
Signals Derivados Complejos
// Multiples dependencias
const items = signal([
{ name: 'Apple', price: 1.5, qty: 3 },
{ name: 'Banana', price: 0.75, qty: 5 },
])
const taxRate = signal(0.1)
// Computed que depende de items y taxRate
const total = computed(() => {
const subtotal = items().reduce(
(sum, item) => sum + item.price * item.qty,
0
)
return subtotal * (1 + taxRate())
})
// Actualizar cualquier dependencia recalcula total
items.update(i => [...i, { name: 'Orange', price: 2, qty: 2 }])Effects con Cleanup
// Effects pueden retornar funcion de cleanup
const userId = signal(1)
effect(() => {
const id = userId()
// Setup: conectar WebSocket
const ws = new WebSocket(`/user/${id}/updates`)
ws.onmessage = (event) => {
console.log('Update:', event.data)
}
// Cleanup: desconectar cuando userId cambie
return () => {
ws.close()
}
})
userId.set(2) // Cierra WS antiguo, abre nuevoBatching de Updates
// Multiples updates en secuencia
const a = signal(1)
const b = signal(2)
const c = signal(3)
const sum = computed(() => a() + b() + c())
effect(() => {
console.log('Sum:', sum())
})
// Sin batching: 3 recalculos
a.set(10)
b.set(20)
c.set(30)
// Con batching: 1 recalculo
batch(() => {
a.set(10)
b.set(20)
c.set(30)
})
Signals vs Otros Enfoques
Comparacion con Redux
// Redux: Mucho boilerplate
const store = createStore(reducer)
const mapStateToProps = state => ({ count: state.count })
const mapDispatchToProps = { increment }
connect(mapStateToProps, mapDispatchToProps)(Component)
// Signals: Directo al punto
const count = signal(0)
const increment = () => count.update(n => n + 1)Comparacion con RxJS
// RxJS: Poderoso pero complejo
const count$ = new BehaviorSubject(0)
const doubled$ = count$.pipe(map(n => n * 2))
doubled$.subscribe(console.log)
count$.next(5)
// Signals: Mas simple para casos comunes
const count = signal(0)
const doubled = computed(() => count() * 2)
effect(() => console.log(doubled()))
count.set(5)Cuando Usar Cada Enfoque
| Escenario | Mejor Opcion |
|---|---|
| Estado local de componente | Signals |
| Streams de datos complejos | RxJS |
| Estado global simple | Signals |
| Estado global complejo | Redux/Zustand |
| Datos asincronos | Signals + async |
| Event sourcing | RxJS |
Performance de Signals
Benchmarks
Signals ofrecen rendimiento superior en escenarios de updates frecuentes.
Prueba: 10,000 updates en cascada:
| Enfoque | Tiempo | Memoria |
|---|---|---|
| Signals (Solid) | 12ms | 2.1MB |
| Signals (Preact) | 15ms | 2.4MB |
| Vue 3 (ref) | 45ms | 4.2MB |
| React (useState) | 180ms | 8.5MB |
| MobX | 28ms | 3.8MB |
Por que Signals son rapidos:
- Sin VDOM diff - Updates directos al DOM
- Tracking preciso - Solo recalcula lo necesario
- Evaluacion perezosa - Computa solo cuando se lee
- Batching automatico - Agrupa updates
Optimizaciones Comunes
// 1. Evita computeds innecesarios
// Malo
const doubled = computed(() => count() * 2)
const quadrupled = computed(() => doubled() * 2) // Encadenamiento
// Mejor
const quadrupled = computed(() => count() * 4)
// 2. Usa untrack para lecturas no reactivas
import { untrack } from 'solid-js'
effect(() => {
const currentCount = count()
// Esta lectura NO crea dependencia
const config = untrack(() => configSignal())
console.log(currentCount, config)
})
// 3. Memoiza computaciones pesadas
const expensiveResult = computed(() => {
return items().map(item => heavyComputation(item))
}, { equals: deepEquals })
El Futuro de los Signals
Estandarizacion TC39
Si la propuesta TC39 es aprobada, tendremos Signals nativos en JavaScript.
Timeline estimado:
- 2024: Stage 1 (actual)
- 2025: Stage 2 (Draft)
- 2026-2027: Stage 3 (Candidate)
- 2028+: Stage 4 (Finished)
Beneficios de la estandarizacion:
- Interoperabilidad entre frameworks
- Performance optimizada por los engines
- Menos dependencias externas
- API consistente
Impacto en los Frameworks
Con Signals nativos, frameworks podrian:
// Hoy: Cada framework tiene su implementacion
import { signal } from 'solid-js' // Solid
import { ref } from 'vue' // Vue
import { signal } from '@angular/core' // Angular
// Futuro: Todos usan la misma base
import { Signal } from 'std:signals' // NativoReact y Signals
React todavia no ha adoptado Signals oficialmente, pero hay discusiones.
Perspectivas:
- React Forget (compiler) resuelve parte del problema
- Signals contradiciria el modelo mental de React
- Posible adopcion parcial o biblioteca oficial
- Comunidad ya usa @preact/signals-react
Conclusion
Signals representan una evolucion natural en la gestion de estado JavaScript. La convergencia de los principales frameworks alrededor de esta primitiva, combinada con la propuesta TC39, sugiere que Signals seran parte fundamental del desarrollo web en los proximos anos.
Puntos principales:
- Signals ofrecen reactividad fina y performante
- Angular, Vue, Solid, Svelte ya adoptaron
- Propuesta TC39 puede hacer Signals nativos
- Performance superior a enfoques tradicionales
- API simple e intuitiva
Recomendaciones:
- Aprende la API de Signals de tu framework
- Experimenta con Solid.js para entender Signals puros
- Acompana la propuesta TC39
- Considera Signals para nuevos proyectos
El futuro de la reactividad en la web esta siendo definido ahora, y Signals estan en el centro de esta transformacion.
Para mas sobre evolucion de frameworks, lee: Vue 3.6 Vapor Mode: La Revolucion de Rendimiento.

