Signals Deep Dive
Core Design
Each signal is a SignalState<T> instance with a version counter and subscriber set:
ts
class SignalState<T> {
private version = 0
private subscribers = new Set<Effect>()
get value(): T {
track(this)
return this._value
}
set value(newValue: T) {
if (Object.is(this._value, newValue)) return
this._value = newValue
this.version++
trigger(this)
}
}How Tracking Works
When a signal's .value is read inside an effect or computed, the signal registers the currently active effect as a subscriber. This creates a dependency graph that knows exactly which effects to notify when a signal changes.
Lazy Computed
computed values only recalculate when their .value is read. If nothing reads a computed, it never executes its derivation function — even if its dependencies change.
Batching Strategy
Multiple signal updates inside a batch() are coalesced into a single notification:
ts
batch(() => {
a.value = 1
b.value = 2
c.value = 3
// effects run once after batch exits
})Why Signals?
Signals outperform Virtual DOM diffing by eliminating the need to diff subtrees whose data hasn't changed. Nexa combines signals with VDOM to get the best of both approaches.