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

TS
computed<T>(fn: () => T): Computed<T>

Parameters

PARAMETERTYPEDESCRIPTION
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.

JS
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.

JS
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().

JS
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

src/islands/computed.ts
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);
live preview● running

See also