Voltar para o Blog

Signals em JavaScript: O Futuro da Reatividade Que Pode Mudar a Web

Ola HaWkers, se voce trabalha com frameworks JavaScript modernos, provavelmente ja ouviu falar de Signals. O que comecou como uma abordagem alternativa ao gerenciamento de estado esta se tornando um consenso entre Angular, Vue, Solid, Svelte e potencialmente entrando na especificacao oficial do JavaScript.

Vamos entender o que sao Signals, por que estao conquistando a comunidade e como eles podem mudar a forma como escrevemos aplicacoes web.

O Que Sao Signals

Conceito Fundamental

Signals sao primitivas reativas que armazenam um valor e notificam automaticamente seus dependentes quando esse valor muda. Diferente do modelo tradicional de re-renderizacao completa, Signals permitem atualizacoes cirurgicas apenas onde necessario.

Anatomia basica de um Signal:

// Criando um signal
const count = signal(0)

// Lendo o valor
console.log(count()) // 0

// Atualizando o valor
count.set(1)
// ou
count.update(n => n + 1)

// Derivando valores (computed)
const doubled = computed(() => count() * 2)

// Reagindo a mudancas (effect)
effect(() => {
  console.log(`Count is: ${count()}`)
})

Por Que Signals Sao Diferentes

A magia dos Signals esta na reatividade fina (fine-grained reactivity). Compare com abordagens tradicionais:

React (re-render completo):

function Counter() {
  const [count, setCount] = useState(0)
  const [name, setName] = useState('User')

  // Quando count muda, TODO o componente re-renderiza
  // Incluindo partes que dependem apenas de name
  return (
    <div>
      <h1>Hello, {name}</h1>
      <p>Count: {count}</p>
      <button onClick={() => setCount(c => c + 1)}>+</button>
    </div>
  )
}

Com Signals (update cirurgico):

function Counter() {
  const count = signal(0)
  const name = signal('User')

  // Quando count muda, APENAS o texto do count atualiza
  // O h1 com name nem e tocado
  return (
    <div>
      <h1>Hello, {name()}</h1>
      <p>Count: {count()}</p>
      <button onClick={() => count.update(c => c + 1)}>+</button>
    </div>
  )
}

A Convergencia dos Frameworks

Quem Esta Usando Signals

Em 2026, praticamente todos os frameworks JavaScript adotaram ou estao adotando Signals.

Status por framework:

Framework Status Signals Desde
Solid.js Nativo (pioneiro) 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 Em discussao -

A Proposta TC39

O mais interessante e que Signals podem se tornar parte do JavaScript nativo. Ha uma proposta ativa no TC39 para adicionar Signals a especificacao.

Status da proposta:

TC39 Proposal: Signals
Stage: 1 (Proposal)
Champions: Daniel Ehrenberg, Rob Riggs

Objetivo: Criar uma primitiva de reatividade padronizada
que funcione como base para todos os frameworks

Proposta de API nativa:

// Possivel 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 Signals Funcionam Por Dentro

O Sistema de Dependencias

Signals usam um grafo de dependencias que e construido automaticamente durante a execucao.

// Internamente, algo assim acontece:

class Signal {
  #value
  #subscribers = new Set()

  constructor(initialValue) {
    this.#value = initialValue
  }

  get() {
    // Se estamos dentro de um effect/computed,
    // registra essa dependencia
    if (currentTracking) {
      this.#subscribers.add(currentTracking)
    }
    return this.#value
  }

  set(newValue) {
    if (this.#value !== newValue) {
      this.#value = newValue
      // Notifica todos os subscribers
      this.#subscribers.forEach(sub => sub.notify())
    }
  }
}

Tracking Automatico

O tracking de dependencias acontece automaticamente quando voce le um signal dentro de um contexto reativo.

const firstName = signal('John')
const lastName = signal('Doe')
const age = signal(30)

// Este computed depende APENAS de firstName e lastName
// age nao e uma dependencia porque nao foi lido
const fullName = computed(() => {
  return `${firstName()} ${lastName()}`
})

// Mudar age NAO recalcula fullName
age.set(31) // fullName nao reage

// Mudar firstName recalcula fullName
firstName.set('Jane') // fullName recalcula

Push vs Pull

Signals usam um modelo hibrido de push-pull:

// PUSH: Quando um signal muda, ele "empurra" notificacao
// para seus dependentes diretos

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 so sao recalculados quando lidos
// (lazy evaluation)

console.log(c()) // Pull: c calcula b, b calcula a

Implementacoes Praticas

Signals no Angular

Angular introduziu Signals como alternativa ao 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 no Solid.js

Solid foi o pioneiro em 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 no Vue 3.6

Vue esta introduzindo Signals atraves do 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>

Padroes Avancados com Signals

Signals Derivados Complexos

// Multiplas 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 e taxRate
const total = computed(() => {
  const subtotal = items().reduce(
    (sum, item) => sum + item.price * item.qty,
    0
  )
  return subtotal * (1 + taxRate())
})

// Atualizar qualquer dependencia recalcula total
items.update(i => [...i, { name: 'Orange', price: 2, qty: 2 }])

Effects com Cleanup

// Effects podem retornar funcao 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 quando userId mudar
  return () => {
    ws.close()
  }
})

userId.set(2) // Fecha WS antigo, abre novo

Batching de Updates

// Multiplos updates em sequencia
const a = signal(1)
const b = signal(2)
const c = signal(3)

const sum = computed(() => a() + b() + c())

effect(() => {
  console.log('Sum:', sum())
})

// Sem batching: 3 recalculos
a.set(10)
b.set(20)
c.set(30)

// Com batching: 1 recalculo
batch(() => {
  a.set(10)
  b.set(20)
  c.set(30)
})

Signals vs Outras Abordagens

Comparacao com Redux

// Redux: Muito boilerplate
const store = createStore(reducer)
const mapStateToProps = state => ({ count: state.count })
const mapDispatchToProps = { increment }
connect(mapStateToProps, mapDispatchToProps)(Component)

// Signals: Direto ao ponto
const count = signal(0)
const increment = () => count.update(n => n + 1)

Comparacao com RxJS

// RxJS: Poderoso mas complexo
const count$ = new BehaviorSubject(0)
const doubled$ = count$.pipe(map(n => n * 2))
doubled$.subscribe(console.log)
count$.next(5)

// Signals: Mais simples para casos comuns
const count = signal(0)
const doubled = computed(() => count() * 2)
effect(() => console.log(doubled()))
count.set(5)

Quando Usar Cada Abordagem

Cenario Melhor Opcao
Estado local de componente Signals
Streams de dados complexos RxJS
Estado global simples Signals
Estado global complexo Redux/Zustand
Dados assincronos Signals + async
Event sourcing RxJS

Performance de Signals

Benchmarks

Signals oferecem performance superior em cenarios de updates frequentes.

Teste: 10.000 updates em cascata:

Abordagem Tempo Memory
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 sao rapidos:

  1. Sem VDOM diff - Updates diretos ao DOM
  2. Tracking preciso - So recalcula o necessario
  3. Lazy evaluation - Computa apenas quando lido
  4. Batching automatico - Agrupa updates

Otimizacoes Comuns

// 1. Evite computeds desnecessarios
// Ruim
const doubled = computed(() => count() * 2)
const quadrupled = computed(() => doubled() * 2) // Encadeamento

// Melhor
const quadrupled = computed(() => count() * 4)

// 2. Use untrack para leituras nao reativas
import { untrack } from 'solid-js'

effect(() => {
  const currentCount = count()
  // Esta leitura NAO cria dependencia
  const config = untrack(() => configSignal())
  console.log(currentCount, config)
})

// 3. Memoize computacoes pesadas
const expensiveResult = computed(() => {
  return items().map(item => heavyComputation(item))
}, { equals: deepEquals })

O Futuro dos Signals

Padronizacao TC39

Se a proposta TC39 for aprovada, teremos Signals nativos no JavaScript.

Timeline estimado:

  • 2024: Stage 1 (atual)
  • 2025: Stage 2 (Draft)
  • 2026-2027: Stage 3 (Candidate)
  • 2028+: Stage 4 (Finished)

Beneficios da padronizacao:

  1. Interoperabilidade entre frameworks
  2. Performance otimizada pelos engines
  3. Menos dependencias externas
  4. API consistente

Impacto nos Frameworks

Com Signals nativos, frameworks poderiam:

// Hoje: Cada framework tem sua implementacao
import { signal } from 'solid-js' // Solid
import { ref } from 'vue' // Vue
import { signal } from '@angular/core' // Angular

// Futuro: Todos usam a mesma base
import { Signal } from 'std:signals' // Nativo

React e Signals

React ainda nao adotou Signals oficialmente, mas ha discussoes.

Perspectivas:

  • React Forget (compiler) resolve parte do problema
  • Signals contradiria o modelo mental do React
  • Possivel adocao parcial ou biblioteca oficial
  • Comunidade ja usa @preact/signals-react

Conclusao

Signals representam uma evolucao natural no gerenciamento de estado JavaScript. A convergencia dos principais frameworks em torno dessa primitiva, combinada com a proposta TC39, sugere que Signals serao parte fundamental do desenvolvimento web nos proximos anos.

Pontos principais:

  1. Signals oferecem reatividade fina e performatica
  2. Angular, Vue, Solid, Svelte ja adotaram
  3. Proposta TC39 pode tornar Signals nativos
  4. Performance superior a abordagens tradicionais
  5. API simples e intuitiva

Recomendacoes:

  • Aprenda a API de Signals do seu framework
  • Experimente Solid.js para entender Signals puros
  • Acompanhe a proposta TC39
  • Considere Signals para novos projetos

O futuro da reatividade na web esta sendo definido agora, e Signals estao no centro dessa transformacao.

Para mais sobre evolucao dos frameworks, leia: Vue 3.6 Vapor Mode: A Revolucao de Performance.

Bora pra cima! 🦅

Comentários (0)

Esse artigo ainda não possui comentários 😢. Seja o primeiro! 🚀🦅

Adicionar comentário