Reactive Programming API Reference
April 11, 2025 ยท View on GitHub
Core Types
Stream
A push-based stream of values that supports multiple subscribers.
class Stream<T> {
// Subscribe to stream values
subscribe(fn: (value: T) => void): () => void;
// Push a new value to subscribers
next(value: T): void;
// Complete the stream
complete(): void;
}
Example Usage
const events = new Stream<MouseEvent>();
const unsubscribe = events.subscribe(event => {
console.log(`Mouse clicked at: ${event.x}, ${event.y}`);
});
// Later: clean up subscription
unsubscribe();
Signal
A reactive value container that notifies subscribers on changes.
class Signal<T> {
constructor(initial: T);
// Get current value
get value(): T;
// Set new value and notify subscribers
set value(newVal: T);
// Subscribe to value changes
subscribe(fn: (newVal: T) => void): () => void;
}
Example Usage
const counter = new Signal<number>(0);
counter.subscribe(value => {
console.log(`Counter updated: ${value}`);
});
counter.value = 42; // Logs: Counter updated: 42
Best Practices
Memory Management
Clean up subscriptions to prevent memory leaks:
class Component {
private subscriptions: Array<() => void> = [];
init() {
// Store subscription cleanup function
this.subscriptions.push(
signal.subscribe(value => this.update(value))
);
}
destroy() {
// Clean up all subscriptions
this.subscriptions.forEach(unsubscribe => unsubscribe());
this.subscriptions = [];
}
}
Error Handling
Handle errors in subscribers:
stream.subscribe(value => {
try {
processValue(value);
} catch (error) {
console.error("Error processing value:", error);
}
});
Performance
Avoid unnecessary updates:
const signal = new Signal<number>(0);
signal.subscribe(value => {
// Only update UI when value actually changes
if (value !== previousValue) {
updateUI(value);
}
});
State Management Patterns
Derived Signals
Create computed values from signals:
function derived<T, U>(signal: Signal<T>, fn: (value: T) => U): Signal<U> {
const derived = new Signal<U>(fn(signal.value));
signal.subscribe(value => {
derived.value = fn(value);
});
return derived;
}
// Usage
const count = new Signal<number>(0);
const doubled = derived(count, n => n * 2);
Combining Signals
Combine multiple signals into one:
function combine<T, U>(signals: Signal<T>[], fn: (...values: T[]) => U): Signal<U> {
const initial = fn(...signals.map(s => s.value));
const combined = new Signal<U>(initial);
signals.forEach(signal => {
signal.subscribe(() => {
combined.value = fn(...signals.map(s => s.value));
});
});
return combined;
}
// Usage
const firstName = new Signal<string>("John");
const lastName = new Signal<string>("Doe");
const fullName = combine(
[firstName, lastName],
(first, last) => `${first} ${last}`
);
Testing
Test reactive components:
describe('Signal', () => {
test('notifies subscribers on value change', () => {
const signal = new Signal<number>(0);
const mockFn = jest.fn();
signal.subscribe(mockFn);
signal.value = 42;
expect(mockFn).toHaveBeenCalledWith(42);
});
});