Signals em JavaScript 2026: O Futuro da Reatividade na Web
Olá HaWkers, uma das propostas mais aguardadas do TC39 está avançando em 2026: Signals nativos no JavaScript. Essa feature promete unificar como frameworks lidam com reatividade e pode mudar fundamentalmente a forma como construímos aplicações web.
Vamos explorar o que são Signals, como funcionam, e o impacto no ecossistema.
O Que São Signals
Conceito Fundamental
// Signal é um container reativo para valores
// Analogia simples
const traditionalVariable = 5; // Valor estático
// Se mudar, ninguém sabe
// Signal
const count = signal(5); // Valor reativo
// Quando mudar, dependentes são notificados automaticamenteComo Funcionam
// Implementação conceitual (não é a API final do TC39)
// 1. Criar um signal
const count = signal(0);
// 2. Ler o valor
console.log(count.value); // 0
// 3. Escrever um valor
count.value = 1; // Notifica dependentes
// 4. Computed (derivado de outros signals)
const doubled = computed(() => count.value * 2);
// doubled.value é sempre count.value * 2
// Recalcula automaticamente quando count muda
// 5. Effect (side effect reativo)
effect(() => {
console.log(`Count is now: ${count.value}`);
});
// Executa sempre que count muda
Por Que Signals Importam
O Problema Atual
// React: re-render de componente inteiro
function Counter() {
const [count, setCount] = useState(0);
// Quando count muda:
// 1. Função Counter executa novamente
// 2. Virtual DOM é criado
// 3. Diff com DOM anterior
// 4. Patches aplicados
return (
<div>
<p>Count: {count}</p>
<p>Doubled: {count * 2}</p>
<button onClick={() => setCount(c => c + 1)}>
Increment
</button>
</div>
);
}
// Mesmo que só count mudou, tudo re-renderiza// Signals: update granular
function Counter() {
const count = signal(0);
const doubled = computed(() => count.value * 2);
// Quando count muda:
// 1. Só o texto que usa count.value atualiza
// 2. Computed doubled recalcula
// 3. Só o texto que usa doubled atualiza
// 4. Nenhum re-render de componente
return (
<div>
<p>Count: {count}</p>
<p>Doubled: {doubled}</p>
<button onClick={() => count.value++}>
Increment
</button>
</div>
);
}
// Update cirúrgico, sem overheadComparativo de Performance
// Benchmark conceitual
const performanceComparison = {
react: {
update: 'Component re-render',
complexity: 'O(tree size)',
overhead: 'Virtual DOM diff',
ideal: 'UIs com muitas mudanças coordenadas'
},
signals: {
update: 'Direct DOM mutation',
complexity: 'O(1) por signal',
overhead: 'Tracking de dependências',
ideal: 'Updates frequentes e granulares'
},
realWorld: {
smallApp: 'Diferença negligível',
largeApp: 'Signals 2-10x mais rápido',
animations: 'Signals muito superior',
forms: 'Signals muito superior'
}
};
Frameworks Que Já Usam Signals
SolidJS
// SolidJS: Signals desde o início
import { createSignal, createEffect, createMemo } from 'solid-js';
function Counter() {
// Signal primitivo
const [count, setCount] = createSignal(0);
// Computed (memo)
const doubled = createMemo(() => count() * 2);
// Effect
createEffect(() => {
console.log('Count changed:', count());
});
return (
<div>
<p>Count: {count()}</p>
<p>Doubled: {doubled()}</p>
<button onClick={() => setCount(c => c + 1)}>
Increment
</button>
</div>
);
}
// Note: count() é função, não .value
// SolidJS usa functions para trackingVue 3
// Vue 3: Composition API com reatividade baseada em Signals
import { ref, computed, watchEffect } from 'vue';
// ref é essencialmente um Signal
const count = ref(0);
// computed é um Signal derivado
const doubled = computed(() => count.value * 2);
// watchEffect é um Effect
watchEffect(() => {
console.log('Count changed:', count.value);
});
// Em template Vue
// <template>
// <p>Count: {{ count }}</p>
// <p>Doubled: {{ doubled }}</p>
// <button @click="count++">Increment</button>
// </template>Angular
// Angular 17+: Signals oficiais
import { signal, computed, effect } from '@angular/core';
@Component({
template: `
<p>Count: {{ count() }}</p>
<p>Doubled: {{ doubled() }}</p>
<button (click)="increment()">Increment</button>
`
})
export class CounterComponent {
// Signal
count = signal(0);
// Computed
doubled = computed(() => this.count() * 2);
constructor() {
// Effect
effect(() => {
console.log('Count changed:', this.count());
});
}
increment() {
this.count.update(c => c + 1);
// ou: this.count.set(this.count() + 1);
}
}Preact Signals
// Preact Signals: funciona em React também!
import { signal, computed, effect } from '@preact/signals-react';
// Signals globais (fora do componente)
const count = signal(0);
const doubled = computed(() => count.value * 2);
function Counter() {
// Sem hooks! Signal é global e reativo
return (
<div>
<p>Count: {count}</p>
<p>Doubled: {doubled}</p>
<button onClick={() => count.value++}>
Increment
</button>
</div>
);
}
// O componente NÃO re-renderiza quando count muda
// Apenas o texto é atualizado diretamente
A Proposta TC39
Status em 2026
// TC39 Signals Proposal
const tc39Proposal = {
stage: 'Stage 2 (rascunho)',
champions: ['Rob Eisenberg (Angular)', 'Daniel Ehrenberg'],
goal: 'Primitivas padrão para reatividade',
apis: {
// Signal básico
'Signal.State': 'Valor reativo mutável',
'Signal.Computed': 'Valor derivado (read-only)',
'Signal.subtle.Watch': 'API de baixo nível para effects',
},
notIncluded: {
effect: 'Frameworks implementam',
batch: 'Frameworks implementam',
rendering: 'Frameworks implementam'
},
philosophy: 'Interoperabilidade, não substituição'
};API Proposta
// API TC39 (sujeita a mudanças)
// Signal.State - valor mutável
const count = new Signal.State(0);
count.get(); // 0
count.set(1);
count.get(); // 1
// Signal.Computed - valor derivado
const doubled = new Signal.Computed(() => count.get() * 2);
doubled.get(); // 2
// doubled.set() não existe - é read-only
// Signal.subtle.Watch - para frameworks
const watcher = new Signal.subtle.Watch(() => {
// Chamado quando dependências mudam
console.log('Something changed');
});
// Frameworks usam isso para implementar effectsPor Que Padronizar
// O problema da fragmentação atual
const currentFragmentation = {
solidjs: 'createSignal() retorna tuple',
vue: 'ref() usa .value',
angular: 'signal() usa funções get/set',
preact: 'signal() usa .value',
svelte5: 'Runes com $state',
qwik: 'useSignal() com .value'
};
// Com TC39 Signals
const futureInterop = {
sharedPrimitive: 'Signal.State e Signal.Computed',
frameworks: 'Wrappers finos sobre primitivas padrão',
libraries: 'Podem ser framework-agnostic',
benefit: 'Compartilhar código reativo entre frameworks'
};
Signals vs useState
Quando Usar Cada Um
// React com useState (padrão atual)
function Form() {
const [name, setName] = useState('');
const [email, setEmail] = useState('');
const [errors, setErrors] = useState({});
// Cada setState causa re-render
return (
<form>
<input
value={name}
onChange={e => setName(e.target.value)}
/>
<input
value={email}
onChange={e => setEmail(e.target.value)}
/>
{/* Re-renderiza tudo a cada keystroke */}
</form>
);
}
// Com Signals (ex: Preact Signals em React)
const name = signal('');
const email = signal('');
const errors = signal({});
function Form() {
// Componente não re-renderiza!
return (
<form>
<input
value={name}
onInput={e => name.value = e.target.value}
/>
<input
value={email}
onInput={e => email.value = e.target.value}
/>
{/* Updates granulares, sem re-render */}
</form>
);
}Trade-offs
// Quando Signals brilham
const signalsIdealFor = [
'Forms com muitos campos',
'Animações e transições',
'Updates de alta frequência',
'Estado compartilhado entre componentes',
'Apps com muita interatividade'
];
// Quando useState é suficiente
const useStateIdealFor = [
'Estado local simples',
'UIs com poucas mudanças',
'Quando re-render é barato',
'Quando você precisa de efeitos no re-render'
];
// Considerações
const considerations = {
devex: 'Signals requer mental model diferente',
debugging: 'useState mais fácil de debugar',
ecosystem: 'React ecosystem assume useState',
future: 'React pode adicionar signals nativos'
};
Implementando Signals Hoje
Em React
// Opção 1: Preact Signals
npm install @preact/signals-react
import { signal, computed } from '@preact/signals-react';
// Opção 2: Jotai (signal-like)
npm install jotai
import { atom, useAtom } from 'jotai';
const countAtom = atom(0);
const doubledAtom = atom((get) => get(countAtom) * 2);
function Counter() {
const [count, setCount] = useAtom(countAtom);
const [doubled] = useAtom(doubledAtom);
// ...
}
// Opção 3: Legend State
npm install @legendapp/state @legendapp/state/react
import { observable } from '@legendapp/state';
import { observer } from '@legendapp/state/react';
const state = observable({ count: 0 });
const Counter = observer(function Counter() {
return <div>{state.count.get()}</div>;
});Em Vue
// Vue 3 já tem signals (refs)
import { ref, computed, watch } from 'vue';
// Composable reativo
function useCounter() {
const count = ref(0);
const doubled = computed(() => count.value * 2);
function increment() {
count.value++;
}
return { count, doubled, increment };
}
// Uso em componente
export default {
setup() {
const { count, doubled, increment } = useCounter();
return { count, doubled, increment };
}
};Em Svelte 5
// Svelte 5 Runes (signals)
<script>
// $state é um signal
let count = $state(0);
// $derived é um computed
let doubled = $derived(count * 2);
// $effect é um effect
$effect(() => {
console.log('Count is', count);
});
function increment() {
count++; // Simples assim!
}
</script>
<button onclick={increment}>
Count: {count}
</button>
<p>Doubled: {doubled}</p>
O Futuro Com TC39 Signals
Cenário 2027+
// Possível futuro com Signals nativos
// JavaScript nativo (sem imports!)
const count = new Signal.State(0);
const doubled = new Signal.Computed(() => count.get() * 2);
// React poderia adotar
function Counter() {
// use() hook para signals
const countValue = use(count);
return <p>Count: {countValue}</p>;
}
// Ou até JSX nativo
<p>Count: {count}</p> // Auto-unwrap em JSX
// Bibliotecas framework-agnostic
class ReactiveDatePicker {
selectedDate = new Signal.State(null);
formattedDate = new Signal.Computed(() =>
this.selectedDate.get()?.toLocaleDateString()
);
// Funciona em React, Vue, Angular, Svelte...
}Impacto no Ecossistema
// O que muda com Signals nativos
const ecosystemImpact = {
frameworks: {
change: 'Wrappers finos sobre primitivas nativas',
benefit: 'Menos código, mais performance',
risk: 'Diferenciação menor entre frameworks'
},
libraries: {
change: 'Podem ser framework-agnostic',
benefit: 'Escreva uma vez, use em qualquer lugar',
example: 'Form library que funciona em React e Vue'
},
learning: {
change: 'Conceito de signal vira fundamento',
benefit: 'Transferência de conhecimento entre frameworks',
requirement: 'Entender reatividade se torna essencial'
},
performance: {
change: 'Otimizações no engine JavaScript',
benefit: 'Signals mais rápidos que qualquer userland',
expectation: 'V8, SpiderMonkey otimizam para signals'
}
};Conclusão
Signals representam uma mudança fundamental em como pensamos sobre reatividade na web. Enquanto o modelo de re-render do React dominou a última década, signals oferecem uma alternativa mais granular e performática.
O que fazer agora:
- Entenda o conceito: Mesmo sem usar, entenda como signals funcionam
- Experimente: Teste Preact Signals, Vue 3, ou Angular 17+
- Acompanhe TC39: A proposta está avançando
- Não abandone React: Signals podem complementar, não substituir
O futuro parece convergir para signals como primitiva padrão de reatividade. Frameworks continuarão existindo, mas com uma base comum que facilita interoperabilidade e aprendizado.
Para entender mais sobre o ecossistema JavaScript atual, leia: VoidZero em 2026.

