REFERENCE / CORE API
computed()
Creates a lazy, cached derivation. It never computes until read, and it never wakes its subscribers when a recompute produces an equal value.
Signature
computed<T>(fn: () => T): Computed<T>
Parameters
fn
() => T
The derivation. Every signal or computed it reads through .value becomes a dependency; it must be pure — creating an effect inside it throws.
Returns
Computed<T> — read through .value (subscribes) or .peek() (does not). There is no setter: c.value = x is a TypeScript error and a runtime TypeError.
Lazy, then cached
Nothing runs at creation. The first read computes; later reads return the cached value until a dependency's write version moves. A computed nobody reads costs nothing, however often its inputs change.
const count = signal(0);
const double = computed(() => count.value * 2);
double.value; // 0 — computed on this first read, then cached
count.value = 2;
double.value; // 4 — recomputed because count changed
The equality cutoff
When a recompute yields a value equal to the previous one (Object.is), propagation stops there: effects and templates downstream are not run. An effect that was scheduled re-verifies on wake and declines when every path to it proves clean.
const n = signal(1);
const sign = computed(() => (n.value < 0 ? 'negative' : 'positive'));
effect(() => console.log('sign is', sign.value)); // logs once
n.value = 2; // sign recomputes to 'positive' — same value, so the effect does NOT run
n.value = -1; // 'negative' — now it runs
Pure by rule
A computed is a value, not a place for work. Side effects belong in effect().
const total = computed(() => {
effect(() => {}); // throws: a computed stays pure
return items.value.length;
});
double.value = 5; // a TypeError at runtime, and a compile error in TypeScript — no setter
When its scope dies
A computed created inside a component or effect belongs to that scope. When the scope is disposed the computed detaches from its sources, so nothing keeps it alive; if something reads it again later it revives and recomputes on demand.
Try it
import { signal, computed, html, mount } from '@amojs.dev/core';
const count = signal(0);
const atLeastFive = computed(() => count.value >= 5);
let evaluations = 0;
// a function hole: re-runs when atLeastFive CHANGES — not on every count write
const label = () => {
evaluations++;
return atLeastFive.value ? 'five or more' : 'under five';
};
const Demo = () => html`<div class="demo">
<div class="row">
<button class="counter" onclick=${() => count.value++}>count: ${count}</button>
</div>
<p>${label}</p>
<p>that text was evaluated <b>${() => (count.value, evaluations)}</b> times</p>
</div>`;
const host = document.getElementById('demo');
if (host) mount(Demo, host);