Back to blog

Signals: The New Reactivity Standard Uniting JavaScript Frameworks

Hello HaWkers, something interesting is happening in the JavaScript ecosystem: after years of each framework having its own approach to state management, a standard is emerging - Signals.

Angular, Vue, Svelte, and Solid now use Signals for reactivity. And there's a movement to add Signals directly to JavaScript specifications.

What Are Signals?

Signals are reactivity primitives that allow efficient tracking and propagation of state changes. Unlike the Virtual DOM model used by React, Signals update only the UI parts that actually changed.

Fundamental concepts:

  • Signal: A value container that can be observed
  • Computed: A derived value that automatically recalculates when its dependencies change
  • Effect: A function that automatically executes when Signals it uses change
// Conceptual Signals example
import { signal, computed, effect } from 'signals';

// Create a signal
const count = signal(0);

// Create a computed value
const doubled = computed(() => count.value * 2);

// Create an effect
effect(() => {
  console.log(`Count: ${count.value}, Doubled: ${doubled.value}`);
});

// Update the signal - effect executes automatically
count.value = 5;
// Output: "Count: 5, Doubled: 10"

Why Are Signals Becoming Standard?

1. Superior Performance

React's Virtual DOM model requires reconciliation of the entire component tree to detect changes. Signals surgically update only what changed.

Simplified comparison:

Approach When Updates Overhead
Virtual DOM Re-renders entire component High
Signals Updates only specific binding Minimal

2. Predictability

With Signals, data flow is explicit. You know exactly what depends on what:

// Dependencies are tracked automatically
const firstName = signal('John');
const lastName = signal('Doe');

// This computed depends on firstName and lastName
const fullName = computed(() => `${firstName.value} ${lastName.value}`);

// Change in firstName updates fullName automatically
firstName.value = 'Jane';
// fullName is now "Jane Doe"

3. No Unnecessary Re-renders

In React, any state change can cause re-render of the entire child component tree. With Signals, only affected bindings update.

Signals in Major Frameworks

Angular Signals

Angular introduced Signals in version 16 and it's now the recommended approach:

import { signal, computed, effect } from '@angular/core';

@Component({
  selector: 'app-counter',
  template: `
    <p>Count: {{ count() }}</p>
    <p>Doubled: {{ doubled() }}</p>
    <button (click)="increment()">+</button>
  `
})
export class CounterComponent {
  count = signal(0);
  doubled = computed(() => this.count() * 2);

  constructor() {
    effect(() => {
      console.log('Count changed:', this.count());
    });
  }

  increment() {
    this.count.update(c => c + 1);
  }
}

Vue 3 Reactivity

Vue uses a similar approach with ref and computed:

import { ref, computed, watchEffect } from 'vue';

const count = ref(0);
const doubled = computed(() => count.value * 2);

watchEffect(() => {
  console.log('Count changed:', count.value);
});

// Template automatically uses
// {{ count }} and {{ doubled }}

Svelte 5 Runes

Svelte 5 introduced "runes" - their version of Signals:

<script>
  let count = $state(0);
  let doubled = $derived(count * 2);

  $effect(() => {
    console.log('Count changed:', count);
  });
</script>

<p>Count: {count}</p>
<p>Doubled: {doubled}</p>
<button onclick={() => count++}>+</button>

SolidJS

Solid pioneered this model and influenced all others:

import { createSignal, createEffect, createMemo } from 'solid-js';

function Counter() {
  const [count, setCount] = createSignal(0);
  const doubled = createMemo(() => count() * 2);

  createEffect(() => {
    console.log('Count changed:', count());
  });

  return (
    <div>
      <p>Count: {count()}</p>
      <p>Doubled: {doubled()}</p>
      <button onClick={() => setCount(c => c + 1)}>+</button>
    </div>
  );
}

The TC39 Proposal for Native Signals

There's an active proposal at TC39 (the committee that defines JavaScript standards) to add Signals natively to the language.

Benefits of native Signals:

  • Interoperability between frameworks
  • Engine-optimized performance
  • Standardized API
  • Smaller bundle size (no need to import from library)

The proposal is still in early stages, but industry support is strong.

What Would Native Signals Look Like?

// Possible future syntax (speculative)
const count = Signal.state(0);
const doubled = Signal.computed(() => count.get() * 2);

Signal.effect(() => {
  console.log(`Count: ${count.get()}`);
});

count.set(5);

What About React?

React follows a different path with its Virtual DOM model and hooks. The React Compiler (formerly React Forget) aims to automatically optimize re-renders.

React's position:

  • Not planning to adopt Signals in core
  • React Compiler + Server Components is the performance strategy
  • Libraries like Jotai and Zustand offer Signals-like APIs

💡 Note: You can use Signals libraries with React, but it's not the framework's idiomatic approach.

Implementing Signals from Scratch

To understand how they work, let's create a simplified version:

// Mini Signals implementation
let currentEffect = null;

function createSignal(initialValue) {
  let value = initialValue;
  const subscribers = new Set();

  const read = () => {
    if (currentEffect) {
      subscribers.add(currentEffect);
    }
    return value;
  };

  const write = (newValue) => {
    value = newValue;
    subscribers.forEach(fn => fn());
  };

  return [read, write];
}

function createEffect(fn) {
  currentEffect = fn;
  fn(); // Execute to collect dependencies
  currentEffect = null;
}

function createMemo(fn) {
  const [getValue, setValue] = createSignal();

  createEffect(() => {
    setValue(fn());
  });

  return getValue;
}

// Usage
const [count, setCount] = createSignal(0);
const doubled = createMemo(() => count() * 2);

createEffect(() => {
  console.log(`Count: ${count()}, Doubled: ${doubled()}`);
});

setCount(5); // Log: "Count: 5, Doubled: 10"

When to Use Signals vs Other Approaches

Signals are ideal for:

  • Local UI state
  • Complex forms
  • Frequent updates to small UI parts
  • Applications that need maximum performance

Other approaches may be better for:

  • Server state (React Query, TanStack Query)
  • Very complex global state
  • Applications where simplicity > performance

Conclusion

Signals represent an interesting convergence in the JavaScript ecosystem. After years of divergent approaches, major frameworks are adopting a similar fine-grained reactivity model.

For developers, this means concepts learned in one framework transfer easily to others. And if the TC39 proposal is approved, we could have native Signals in JavaScript.

If you're interested in JavaScript and framework trends, I recommend checking out another article: Why Developers Are Ditching Frameworks and Returning to Vanilla JavaScript where you'll discover the other side of this discussion.

Let's go! 🦅

Comments (0)

This article has no comments yet 😢. Be the first! 🚀🦅

Add comments