Signals
A signal is the unit of reactive state. signal returns one binding: call it to read, .set() to write.
import { signal } from '@fluixi/reactive/signal';
const count = signal(0);
count(); // read → 0
count.set(1); // write → 1
count.set(c => c + 1); // update from the previous value → 2
Inside a component, $signal is the same thing without the import — the compiler resolves it
and adds what it needs:
const count = $signal(0);
Reading is tracking
Calling the signal inside a memo or effect subscribes that computation to the signal. The next write re-runs only the computations that read it:
effect(() => {
console.log('count is', count()); // subscribes to count
});
count.set(5); // logs "count is 5"
Reading outside any reactive scope just returns the value — no subscription.
Equality
By default a signal skips notifying readers when the new value is === the old one. Pass equals
to customize, or equals: false to always notify:
const list = signal([], { equals: false });
Reading without subscribing
Use untrack to read a signal without creating a dependency:
import { untrack } from '@fluixi/reactive/signal';
effect(() => {
draw(count(), untrack(theme)); // re-runs on count, but not on theme
});
$untrack is the same without the import.
The tuple form
createSignal returns the same signal as a [read, write] pair:
import { createSignal } from '@fluixi/reactive/signal';
const [count, setCount] = createSignal(0);
count(); // read
setCount(1); // write
These are one primitive with two spellings, not two implementations — signal is
createSignal with the pair packed into an object, and its get/set are that pair. So
they track each other, mix freely in a module, and behave identically through SSR and
hydration. Prefer signal; reach for the tuple when you want the halves apart, and note that
plenty of existing code and libraries are written against it.
Next: Derived values.