computed
A read-only signal that automatically updates when dependencies change.
API
function computed<T>(getter: (previousValue?: T) => T): () => T
getter
: A function that computes the value. It receives the previously computed value as an optional argument.- Returns: A read-only signal.
TypeScript
The computed
function infers its type from the getter
’s return value.
import { signal, computed, ReadonlySignal } from '@hellajs/core';
const count = signal(0);
// Type is inferred as ReadonlySignal<number>
const double = computed(() => count() * 2);
// Explicit typing
type User = { firstName: string; lastName: string };
const user = signal<User>({ firstName: 'John', lastName: 'Doe' });
const fullName = computed<string>(() => `${user().firstName} ${user().lastName}`);
// The ReadonlySignal<T> type represents the returned function
const derivedValue: ReadonlySignal<string> = computed(() => `Count: ${count()}`);
Basic Usage
Computed signals update automatically when any of their dependencies (signals read during execution) change.
import { signal, computed } from '@hellajs/core';
const firstName = signal("John");
const lastName = signal("Doe");
const fullName = computed(() => `${firstName()} ${lastName()}`);
console.log(fullName()); // "John Doe"
firstName("Jane");
console.log(fullName()); // "Jane Doe"
Key Concepts
Lazy Evaluation
Computed signals are lazy. They only recompute when their value is requested and a dependency has changed.
const a = signal(1);
const b = signal(2);
const sum = computed(() => {
console.log('Computing sum...');
return a() + b();
});
// Computation does not run if the value is not accessed
a(10);
// Computation runs now, because sum() is accessed
console.log(sum()); // Logs: "Computing sum..." then "12"
// Accessing again returns the cached value without recomputing
console.log(sum()); // "12"
Chaining Computations
Computed signals can depend on other computed signals, creating an efficient graph of reactive dependencies.
const price = signal(100);
const quantity = signal(2);
const subtotal = computed(() => price() * quantity());
const tax = computed(() => subtotal() * 0.07); // 7% tax
const total = computed(() => subtotal() + tax());
console.log(total()); // 214
quantity(3);
console.log(total()); // 321
Conditional Dependencies
A computed signal only depends on the signals that are actually read during its last execution.
const showAdvanced = signal(false);
const basic = signal('basic');
const advanced = signal('advanced');
const setting = computed(() => {
if (showAdvanced()) {
return `Advanced: ${advanced()}`;
}
return `Basic: ${basic()}`;
});
// Initially, `setting` only depends on `showAdvanced` and `basic`.
// Changing `advanced` will not trigger a re-computation.
advanced('new advanced');
console.log(setting()); // "Basic: basic"
// After this, `setting` depends on `showAdvanced` and `advanced`.
showAdvanced(true);
console.log(setting()); // "Advanced: new advanced"
Previous Value Optimization
The getter
function receives the previously computed value as its first argument, enabling incremental calculations and performance optimizations.
const numbers = signal([1, 2, 3]);
const sum = computed((previousSum) => {
const currentNumbers = numbers();
// Optimization: if we have a previous sum, we can potentially use it
if (previousSum !== undefined) {
// For this example, just do full computation
// Real optimizations would need to track previous state
return currentNumbers.reduce((acc, n) => acc + n, 0);
}
// Full computation
return currentNumbers.reduce((acc, n) => acc + n, 0);
});
Important Considerations
Keep Getters Pure
The getter
function should not have side effects. Use effect
for side effects.
// ❌ Side effects in computed
const result = computed(() => {
fetch('/api/data');
return count() * 2;
});
// ✅ Pure computation only
const doubled = computed(() => count() * 2);
Avoid Complex Logic
Break complex computations into smaller, chained computed signals for better performance and debugging.
// ✅ Split complex logic
const filtered = computed(() => data().filter(item => item.active));
const sorted = computed(() => [...filtered()].sort());