Reactivity API
Core reactive primitives for state management.
signal
Create reactive state. Primitives are wrapped in { value }, objects become reactive proxies.
function signal<T extends Primitive>(target: T): PrimitiveSignal<T>;
function signal<T extends object>(target: T): Signal<T>;
Parameters
| Name | Type | Description |
|---|---|---|
target | T | Initial value (primitive or object) |
Returns
- Primitives:
{ value: T }wrapper with reactivevalueproperty - Objects: Reactive proxy with direct property access
Examples
import { signal } from 'sigx';
// Primitives - use .value
const count = signal(0);
count.value++; // 1
const name = signal('World');
name.value = 'SignalX';
// Objects - direct access
const user = signal({ name: 'John', age: 30 });
user.name = 'Jane'; // Reactive
user.age++; // Reactive
// Arrays
const items = signal([1, 2, 3]);
items.push(4); // Reactive
items[0] = 10; // Reactive
// Collections
const map = signal(new Map());
map.set('key', 'value'); // Reactive
// Replace entire object
user.$set({ name: 'Bob', age: 25 });
effect
Run a function when its reactive dependencies change.
function effect(fn: EffectFn): EffectRunner;
Parameters
| Name | Type | Description |
|---|---|---|
fn | () => void | Function to run reactively |
Returns
EffectRunner - Callable function with a stop() method.
Examples
import { signal, effect } from 'sigx';
const state = signal({ count: 0 });
// Effect runs immediately and on changes
const runner = effect(() => {
console.log('Count:', state.count);
});
state.count = 5; // Logs: "Count: 5"
// Stop the effect
runner.stop();
state.count = 10; // No log
computed
Create a lazily-evaluated derived value.
function computed<T>(getter: () => T): Computed<T>;
function computed<T>(options: { get: () => T; set: (v: T) => void }): WritableComputed<T>;
Parameters
| Name | Type | Description |
|---|---|---|
getter | () => T | Function that computes the value |
options.get | () => T | Getter for writable computed |
options.set | (v: T) => void | Setter for writable computed |
Returns
- Read-only:
Computed<T>- Call to get value:computed() - Writable:
WritableComputed<T>- Hasset()method
Examples
import { signal, computed } from 'sigx';
const state = signal({ count: 0 });
// Read-only computed
const doubled = computed(() => state.count * 2);
console.log(doubled()); // 0
state.count = 5;
console.log(doubled()); // 10
// Writable computed
const celsius = signal({ value: 0 });
const fahrenheit = computed({
get: () => celsius.value * 9/5 + 32,
set: (f) => celsius.value = (f - 32) * 5/9
});
fahrenheit.set(212);
console.log(celsius.value); // 100
watch
Watch reactive sources with a callback.
function watch<T>(
source: WatchSource<T>,
callback: WatchCallback<T>,
options?: WatchOptions
): WatchHandle;
Parameters
| Name | Type | Description |
|---|---|---|
source | T | () => T | Value or getter to watch |
callback | (value, oldValue, onCleanup) => void | Called on change |
options.immediate | boolean | Run immediately (default: false) |
options.deep | boolean | number | Deep watch (default: false) |
options.once | boolean | Run once then stop |
Returns
WatchHandle - Object with stop(), pause(), resume() methods.
Examples
import { signal, watch } from 'sigx';
const state = signal({ count: 0 });
// Basic watch
const handle = watch(
() => state.count,
(newVal, oldVal) => {
console.log(`Changed from ${oldVal} to ${newVal}`);
}
);
state.count = 5; // Logs: "Changed from 0 to 5"
// With cleanup
watch(
() => state.count,
(value, _, onCleanup) => {
const timer = setInterval(() => console.log(value), 1000);
onCleanup(() => clearInterval(timer));
}
);
// Pause/resume
handle.pause();
state.count = 10; // No log
handle.resume(); // Catches up if value changed
// Stop watching
handle.stop();
batch
Group multiple updates into a single reactive flush.
function batch(fn: () => void): void;
Parameters
| Name | Type | Description |
|---|---|---|
fn | () => void | Function containing updates |
Examples
import { signal, effect, batch } from 'sigx';
const state = signal({ a: 0, b: 0, c: 0 });
effect(() => {
console.log(state.a, state.b, state.c);
});
// Without batch: effect runs 3 times
state.a = 1;
state.b = 2;
state.c = 3;
// With batch: effect runs once
batch(() => {
state.a = 1;
state.b = 2;
state.c = 3;
});
untrack
Read values without creating dependencies.
function untrack<T>(fn: () => T): T;
Parameters
| Name | Type | Description |
|---|---|---|
fn | () => T | Function to execute |
Returns
The return value of fn.
Examples
import { signal, effect, untrack } from 'sigx';
const state = signal({ count: 0, multiplier: 2 });
effect(() => {
// Depends on count, NOT multiplier
const mult = untrack(() => state.multiplier);
console.log(state.count * mult);
});
state.count++; // Effect runs
state.multiplier++; // Effect does NOT run
toRaw
Get the underlying non-reactive object from a proxy.
function toRaw<T>(observed: T): T;
Parameters
| Name | Type | Description |
|---|---|---|
observed | T | Reactive proxy or any value |
Returns
The raw object, or the value itself if not reactive.
Examples
import { signal, toRaw } from 'sigx';
const state = signal({ name: 'John', items: [1, 2, 3] });
// Get raw for serialization
const raw = toRaw(state);
fetch('/api', { body: JSON.stringify(raw) });
// Check identity
const obj = { x: 1 };
const reactive = signal(obj);
toRaw(reactive) === obj; // true
isReactive
Check if a value is a reactive proxy.
function isReactive(value: unknown): boolean;
Parameters
| Name | Type | Description |
|---|---|---|
value | unknown | Value to check |
Returns
true if the value is a reactive proxy.
Examples
import { signal, isReactive } from 'sigx';
const state = signal({ count: 0 });
const plain = { count: 0 };
isReactive(state); // true
isReactive(plain); // false
effectScope
Create a scope for grouping effects.
function effectScope(detached?: boolean): EffectScope;
Parameters
| Name | Type | Default | Description |
|---|---|---|---|
detached | boolean | false | Independent of parent scope |
Returns
EffectScope with:
run<T>(fn: () => T): T | undefined- Execute within scopestop(): void- Stop all effects in scope
Examples
import { signal, effect, effectScope } from 'sigx';
const scope = effectScope();
scope.run(() => {
const state = signal({ count: 0 });
effect(() => console.log(state.count));
effect(() => console.log(state.count * 2));
});
// Stop all effects at once
scope.stop();
Types
Signal<T>
type Signal<T> = T & {
$set: (newValue: T) => void;
};
PrimitiveSignal<T>
type PrimitiveSignal<T> = Signal<{ value: Widen<T> }>;
EffectRunner
interface EffectRunner<T = void> {
(): T;
stop: () => void;
}
Computed<T>
interface Computed<T> {
(): T;
}
WritableComputed<T>
interface WritableComputed<T> {
(): T;
set(value: T): void;
}
WatchHandle
interface WatchHandle {
(): void; // Callable to stop
stop: () => void;
pause: () => void;
resume: () => void;
}
EffectScope
type EffectScope = {
run<T>(fn: () => T): T | undefined;
stop(fromParent?: boolean): void;
}