Signals en JavaScript: Le Futur de la Reactivite Qui Peut Changer le Web
Salut HaWkers, si vous travaillez avec des frameworks JavaScript modernes, vous avez probablement deja entendu parler de Signals. Ce qui a commence comme une approche alternative a la gestion d'etat devient un consensus parmi Angular, Vue, Solid, Svelte et potentiellement entrer dans la specification officielle de JavaScript.
Comprenons ce que sont les Signals, pourquoi ils conquierent la communaute et comment ils peuvent changer la facon dont nous ecrivons des applications web.
Que Sont les Signals
Concept Fondamental
Les Signals sont des primitives reactives qui stockent une valeur et notifient automatiquement leurs dependants quand cette valeur change. Contrairement au modele traditionnel de re-rendu complet, les Signals permettent des mises a jour chirurgicales uniquement la ou c'est necessaire.
Anatomie basique d'un Signal:
// Creer un signal
const count = signal(0)
// Lire la valeur
console.log(count()) // 0
// Mettre a jour la valeur
count.set(1)
// ou
count.update(n => n + 1)
// Deriver des valeurs (computed)
const doubled = computed(() => count() * 2)
// Reagir aux changements (effect)
effect(() => {
console.log(`Count is: ${count()}`)
})Pourquoi les Signals Sont Differents
La magie des Signals reside dans la reactivite fine (fine-grained reactivity). Comparez avec les approches traditionnelles:
React (re-render complet):
function Counter() {
const [count, setCount] = useState(0)
const [name, setName] = useState('User')
// Quand count change, TOUT le composant re-rend
// Y compris les parties qui dependent seulement de name
return (
<div>
<h1>Hello, {name}</h1>
<p>Count: {count}</p>
<button onClick={() => setCount(c => c + 1)}>+</button>
</div>
)
}Avec Signals (update chirurgical):
function Counter() {
const count = signal(0)
const name = signal('User')
// Quand count change, SEUL le texte du count se met a jour
// Le h1 avec name n'est meme pas touche
return (
<div>
<h1>Hello, {name()}</h1>
<p>Count: {count()}</p>
<button onClick={() => count.update(c => c + 1)}>+</button>
</div>
)
}
La Convergence des Frameworks
Qui Utilise les Signals
En 2026, pratiquement tous les frameworks JavaScript ont adopte ou adoptent les Signals.
Statut par framework:
| Framework | Statut Signals | Depuis |
|---|---|---|
| Solid.js | Natif (pionnier) | 2021 |
| Angular | Signals API | 2023 |
| Vue 3.6 | Alien Signals | 2026 |
| Svelte 5 | Runes ($state) | 2024 |
| Preact | @preact/signals | 2022 |
| Qwik | Signals natifs | 2022 |
| React | En discussion | - |
La Proposition TC39
Le plus interessant est que les Signals peuvent devenir partie de JavaScript natif. Il y a une proposition active au TC39 pour ajouter les Signals a la specification.
Statut de la proposition:
TC39 Proposal: Signals
Stage: 1 (Proposal)
Champions: Daniel Ehrenberg, Rob Riggs
Objectif: Creer une primitive de reactivite standardisee
qui fonctionne comme base pour tous les frameworksProposition d'API native:
// Possible API native (speculative)
const count = Signal.state(0)
const doubled = Signal.computed(() => count.get() * 2)
Signal.effect(() => {
console.log(doubled.get())
})
count.set(5) // Logs: 10
Comment les Signals Fonctionnent en Interne
Le Systeme de Dependances
Les Signals utilisent un graphe de dependances qui est automatiquement construit pendant l'execution.
// En interne, quelque chose comme ca se passe:
class Signal {
#value
#subscribers = new Set()
constructor(initialValue) {
this.#value = initialValue
}
get() {
// Si on est dans un effect/computed,
// enregistre cette dependance
if (currentTracking) {
this.#subscribers.add(currentTracking)
}
return this.#value
}
set(newValue) {
if (this.#value !== newValue) {
this.#value = newValue
// Notifie tous les subscribers
this.#subscribers.forEach(sub => sub.notify())
}
}
}Tracking Automatique
Le tracking des dependances se fait automatiquement quand vous lisez un signal dans un contexte reactif.
const firstName = signal('John')
const lastName = signal('Doe')
const age = signal(30)
// Ce computed depend SEULEMENT de firstName et lastName
// age n'est pas une dependance car il n'a pas ete lu
const fullName = computed(() => {
return `${firstName()} ${lastName()}`
})
// Changer age NE recalcule PAS fullName
age.set(31) // fullName ne reagit pas
// Changer firstName recalcule fullName
firstName.set('Jane') // fullName recalculePush vs Pull
Les Signals utilisent un modele hybride push-pull:
// PUSH: Quand un signal change, il "pousse" notification
// a ses dependants directs
const a = signal(1)
const b = computed(() => a() * 2) // b depend de a
const c = computed(() => b() + 10) // c depend de b
a.set(2) // Push: a notifie b, b notifie c
// PULL: Les valeurs ne sont recalculees que quand lues
// (evaluation paresseuse)
console.log(c()) // Pull: c calcule b, b calcule a
Implementations Pratiques
Signals dans Angular
Angular a introduit les Signals comme alternative a RxJS pour les cas 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 dans Solid.js
Solid a ete le pionnier des Signals modernes.
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 dans Vue 3.6
Vue introduit les Signals a travers le 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>
Patterns Avances avec Signals
Signals Derives Complexes
// Dependances multiples
const items = signal([
{ name: 'Apple', price: 1.5, qty: 3 },
{ name: 'Banana', price: 0.75, qty: 5 },
])
const taxRate = signal(0.1)
// Computed qui depend de items et taxRate
const total = computed(() => {
const subtotal = items().reduce(
(sum, item) => sum + item.price * item.qty,
0
)
return subtotal * (1 + taxRate())
})
// Mettre a jour n'importe quelle dependance recalcule total
items.update(i => [...i, { name: 'Orange', price: 2, qty: 2 }])Effects avec Cleanup
// Les effects peuvent retourner une fonction de cleanup
const userId = signal(1)
effect(() => {
const id = userId()
// Setup: connecter WebSocket
const ws = new WebSocket(`/user/${id}/updates`)
ws.onmessage = (event) => {
console.log('Update:', event.data)
}
// Cleanup: deconnecter quand userId change
return () => {
ws.close()
}
})
userId.set(2) // Ferme ancien WS, ouvre nouveauBatching des Updates
// Updates multiples en sequence
const a = signal(1)
const b = signal(2)
const c = signal(3)
const sum = computed(() => a() + b() + c())
effect(() => {
console.log('Sum:', sum())
})
// Sans batching: 3 recalculs
a.set(10)
b.set(20)
c.set(30)
// Avec batching: 1 recalcul
batch(() => {
a.set(10)
b.set(20)
c.set(30)
})
Signals vs Autres Approches
Comparaison avec Redux
// Redux: Beaucoup de boilerplate
const store = createStore(reducer)
const mapStateToProps = state => ({ count: state.count })
const mapDispatchToProps = { increment }
connect(mapStateToProps, mapDispatchToProps)(Component)
// Signals: Droit au but
const count = signal(0)
const increment = () => count.update(n => n + 1)Comparaison avec RxJS
// RxJS: Puissant mais complexe
const count$ = new BehaviorSubject(0)
const doubled$ = count$.pipe(map(n => n * 2))
doubled$.subscribe(console.log)
count$.next(5)
// Signals: Plus simple pour les cas courants
const count = signal(0)
const doubled = computed(() => count() * 2)
effect(() => console.log(doubled()))
count.set(5)Quand Utiliser Chaque Approche
| Scenario | Meilleure Option |
|---|---|
| Etat local de composant | Signals |
| Streams de donnees complexes | RxJS |
| Etat global simple | Signals |
| Etat global complexe | Redux/Zustand |
| Donnees asynchrones | Signals + async |
| Event sourcing | RxJS |
Performance des Signals
Benchmarks
Les Signals offrent une performance superieure dans les scenarios de mises a jour frequentes.
Test: 10 000 updates en cascade:
| Approche | Temps | Memoire |
|---|---|---|
| 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 |
Pourquoi les Signals sont rapides:
- Pas de diff VDOM - Updates directs au DOM
- Tracking precis - Ne recalcule que le necessaire
- Evaluation paresseuse - Calcule seulement quand lu
- Batching automatique - Groupe les updates
Optimisations Courantes
// 1. Evitez les computeds inutiles
// Mauvais
const doubled = computed(() => count() * 2)
const quadrupled = computed(() => doubled() * 2) // Chainement
// Mieux
const quadrupled = computed(() => count() * 4)
// 2. Utilisez untrack pour lectures non reactives
import { untrack } from 'solid-js'
effect(() => {
const currentCount = count()
// Cette lecture NE cree PAS de dependance
const config = untrack(() => configSignal())
console.log(currentCount, config)
})
// 3. Memoize les calculs lourds
const expensiveResult = computed(() => {
return items().map(item => heavyComputation(item))
}, { equals: deepEquals })
Le Futur des Signals
Standardisation TC39
Si la proposition TC39 est approuvee, nous aurons des Signals natifs en JavaScript.
Timeline estimee:
- 2024: Stage 1 (actuel)
- 2025: Stage 2 (Draft)
- 2026-2027: Stage 3 (Candidate)
- 2028+: Stage 4 (Finished)
Avantages de la standardisation:
- Interoperabilite entre frameworks
- Performance optimisee par les engines
- Moins de dependances externes
- API consistante
Impact sur les Frameworks
Avec des Signals natifs, les frameworks pourraient:
// Aujourd'hui: Chaque framework a son implementation
import { signal } from 'solid-js' // Solid
import { ref } from 'vue' // Vue
import { signal } from '@angular/core' // Angular
// Futur: Tous utilisent la meme base
import { Signal } from 'std:signals' // NatifReact et Signals
React n'a pas encore adopte officiellement les Signals, mais il y a des discussions.
Perspectives:
- React Forget (compiler) resout une partie du probleme
- Les Signals contrediraient le modele mental de React
- Possible adoption partielle ou bibliotheque officielle
- La communaute utilise deja @preact/signals-react
Conclusion
Les Signals representent une evolution naturelle dans la gestion d'etat JavaScript. La convergence des principaux frameworks autour de cette primitive, combinee avec la proposition TC39, suggere que les Signals seront une partie fondamentale du developpement web dans les annees a venir.
Points principaux:
- Les Signals offrent une reactivite fine et performante
- Angular, Vue, Solid, Svelte ont deja adopte
- La proposition TC39 peut rendre les Signals natifs
- Performance superieure aux approches traditionnelles
- API simple et intuitive
Recommandations:
- Apprenez l'API Signals de votre framework
- Essayez Solid.js pour comprendre les Signals purs
- Suivez la proposition TC39
- Considerez les Signals pour les nouveaux projets
Le futur de la reactivite web est en train d'etre defini maintenant, et les Signals sont au centre de cette transformation.
Pour en savoir plus sur l'evolution des frameworks, lisez: Vue 3.6 Vapor Mode: La Revolution de Performance.

