Zum Hauptinhalt springen
tsecurity.de LIVE
Echtzeit-Radar & Feeds
Alle RSS Feeds
👥 Community & Social
Sichere ProgrammierungFliproom: a room changeover is a content problem(21.09.2026 um 04:10 Uhr)
Sichere ProgrammierungNova Adiutrix: My Second Agent Built My First Project's To-Do List(21.09.2026 um 04:11 Uhr)
Sichere ProgrammierungFliproom: a room changeover is a content problem(21.09.2026 um 04:10 Uhr)
Sichere ProgrammierungNova Adiutrix: My Second Agent Built My First Project's To-Do List(21.09.2026 um 04:11 Uhr)
Intelligence View
⚡ tsecurity.de Intelligence

How Computed Values Really Work: Lazy Evaluation in a Reactive Graph

Reagiere als Erste:r — dein Feedback zählt!

Recap

Before we continue, let’s quickly review what we already have:

1. graph.ts

Node { kind, deps, subs }, link / unlink, withObserver / track
(reused from Implementing Effect (I))

2. EffectRegistry

Effects.get / set / delete(node: Node)
(reused from Implementing Effect (II))

3. signal.ts

set() iterates over subs, and for effects calls
Effects.get(sub)?.schedule()
(we’ll add a branch for computed below)

What Kind of “Memo” Is This computed?

TL;DR

This computed is equivalent to Vue’s computed or Solid’s createMemo not React’s useMemo.

It is a node in the reactive dataflow graph that:

  • Automatically tracks dependencies
  • Recomputes lazily (only when read)
  • Is not bound to any component lifecycle

Comparison Table

Aspect This article’s computed (signals) Vue computed Solid createMemo React useMemo
Dependency declaration Automatic (read = subscribe) Automatic Automatic Manual deps array
When it computes Lazy, recompute only when stale and read Lazy Lazy During render
Lifecycle Independent of components Tied to instance Tied to root Bound to render
Role in dataflow graph Observer + Trackable (deps + subs) Same Same Not a reactive node
Suitable for side effects No No No No

Examples

Vue

const a = ref(1);
const b = ref(2);
const sum = computed(() => a.value + b.value);

Solid

const [a] = createSignal(1);
const [b] = createSignal(2);
const sum = createMemo(() => a() + b());

This Article (signals)

const a = signal(1);
const b = signal(2);
const sum = computed(() => a.get() + b.get()); // auto-tracked, lazy

React (for contrast only)

const [a, setA] = useState(1);
const [b, setB] = useState(2);

// Render-time cache only; not part of the reactive graph
const sum = useMemo(() => a + b, [a, b]);

The computed we’re about to implement is the kind that:

  • Computes only when read
  • Caches the result
  • Has explicit upstream/downstream edges in the graph.

Design Considerations

  • A computed is both:
    • An Observer (it depends on others → has deps)
    • And a Trackable (others depend on it → has subs)
  • Two internal states:
    • stale: boolean — dirty flag
    • computing: boolean — reentrancy guard (cycle detection)
  • equals(a, b) — skip cache updates when values are equal (default: Object.is)

Who Depends on Whom?

In the diagram below, arrows mean “I depend on this.”

A computed sits in the middle: it has both upstream and downstream edges.

  • signal: only depended on (no deps)
  • effect: only depends on others (no subs)
  • computed: both sides

Push vs Pull: From set() to Recompute

  • signal.set() only pushes invalidation:
    • marks computed as stale,
    • schedules effects via the registry.
  • Actual computation happens later, when computed.get() is called.

Key points:

  • EffectRegistry: maps Node → EffectInstance (via Symbol or WeakMap)
  • Microtask batching: multiple set() calls in the same tick trigger only one effect run.

Lazy Evaluation Flow of computed.get()

  • Only when read do we check stale
  • If dirty:
    • Unlink old dependencies
    • Recompute inside a tracking context
    • Cache the result

Push Invalidation, Pull Computation

Push

signal.set()
→ mark computed as stale
→ schedule effects

Pull

computed.get()
→ recompute only if stale

Cycle protection

reading itself while computing throws an error.

Implementing computed

// computed.ts
import { link, unlink, withObserver, track, type Node } from "./graph";
import { SymbolRegistry as Effects } from "./registry";

type Comparator<T> = (a: T, b: T) => boolean;
const defaultEquals = Object.is;

/** Used by signal.set() and other computed nodes to mark dirty */
export function markStale(node: Node) {
  if (node.kind !== "computed") return;
  const c = node as Node & { stale: boolean };
  if (c.stale) return; // already dirty
  c.stale = true;

  // Propagate downstream
  for (const sub of node.subs) {
    if (sub.kind === "computed") {
      markStale(sub); // cascade to downstream computed nodes
    } else if (sub.kind === "effect") {
      Effects.get(sub)?.schedule(); // enqueue effect
    }
  }
}

export function computed<T>(
  fn: () => T,
  equals: Comparator<T> = defaultEquals
) {
  const node: Node & {
    kind: "computed";
    value: T;
    stale: boolean;
    equals: Comparator<T>;
    computing: boolean;
    hasValue: boolean;
  } = {
    kind: "computed",
    deps: new Set(),
    subs: new Set(),
    value: undefined as unknown as T,
    stale: true, // compute on first read
    equals,
    computing: false,
    hasValue: false,
  };

  function recompute() {
    if (node.computing) throw new Error("Cycle detected in computed");
    node.computing = true;

    // Remove old dependencies (avoid dependency drift leaks)
    for (const d of [...node.deps]) unlink(node, d);

    // Track new dependencies during execution
    const next = withObserver(node, fn);

    if (!node.hasValue || !node.equals(node.value, next)) {
      node.value = next;
      node.hasValue = true;
    }
    node.stale = false;
    node.computing = false;
  }

  const get = () => {
    track(node); // allow observers to subscribe
    if (node.stale || !node.hasValue) recompute();
    return node.value;
  };

  const peek = () => node.value;

  const dispose = () => {
    // Remove all upstream and downstream links
    for (const d of [...node.deps]) unlink(node, d);
    for (const s of [...node.subs]) unlink(s, node);
    node.deps.clear();
    node.subs.clear();
    node.stale = true;
    node.hasValue = false;
  };

  // peek, dispose, and _node are for testing convenience
  return { get, peek, dispose, _node: node };
}

Wiring computed into signal.set()

Add a computed branch in the subscription loop:

// signal.ts
import { markStale } from "./computed";

// inside set()
for (const sub of node.subs) {
  if (sub.kind === "effect") {
    Effects.get(sub)?.schedule();
  } else if (sub.kind === "computed") {
    markStale(sub); // mark dirty only, no immediate recompute
  }
}

This gives us:

  • Push invalidation (dirty marking)
  • Pull computation (lazy recompute on read)

Effects are scheduled, but real recomputation only happens when computed.get() is called inside the effect callback.

Usage Example

const a = signal(1);
const b = signal(2);

const sum = computed(() => a.get() + b.get());   // 3
const double = computed(() => sum.get() * 2);    // 6

const stop = createEffect(() => {
  console.log("double =", double.get());
  onCleanup(() => console.log("cleanup"));
});

a.set(5); 
// marks sum & double as stale; schedules effect
// microtask: cleanup → "double = 14" (recompute happens here)

stop();

Conclusion

So far, we’ve completed:

  • signal.set() Push invalidation — mark computed as stale, schedule effects
  • computed.get() Pull computation — recompute lazily in a tracking context
  • EffectRegistry Node → EffectInstance lookup (via Symbol or WeakMap)

In the next article, we’ll introduce Batching & Transactions to eliminate unnecessary repeated updates.

Ähnliche Beiträge
🔍 Verwandte News

Auch interessante Nachrichten How Computed Values Really Work: Lazy Evaluation in a Reactive Graph

Thematisch verwandte Begriffe: Computed, Values, Really, Work · 6 Treffer

Laden...

Beiträge werden geladen ...

Laden...

Videos werden geladen ...

Laden...

Beiträge werden geladen ...

Laden...

Videos werden geladen ...

Laden...

Beiträge werden geladen ...

Laden...

Videos werden geladen ...

Laden...

Beiträge werden geladen ...

Laden...

Videos werden geladen ...

Zum Aktualisieren ziehen
ZERO-DAY CVE-2026-93977 | A vulnerability was determined in code-projects Assessment Management 1.…
Advisory →
TTS Reader • tsecurity.de Voice
tsecurity.de Icon
tsecurity.de App
Offline-Lesen, Eilmeldungen & 0ms Ladezeit

Installiere tsecurity.de direkt auf deinen Home-Bildschirm für das ultimative Vollbild-Magazinerlebnis ohne Browser-Leisten.

Nächster Beitrag
Themen-Radar & Intelligence Matrix
Echtzeit-Taxonomie nach Angriffsvektoren & Plattformen

tsecurity.de Live Threat Radar

🔴 LIVE RADAR
MONITORING
AKTIV
CVE-DATENBANK
LIVE
🔍
Community Radar & Live Chat
Sentinel Bot online • Live-Stream
Dein Cluster: Security Explorer
Match:
lädt…
Verbindung zum Community-Stream wird aufgebaut...
Bearbeitungsmodus — Senden überschreibt deine Nachricht
Community-Puls — was gerade passiert
lädt…
Aktivitäten deiner Analysten
lädt…
Neues Thema oder Eilmeldung einreichen

Reiche interessante Links, Zero-Days oder Debatten ein. Die Community entscheidet per Upvote über die Veröffentlichung.

Heiß diskutierte Einreichungen
🔖 Gespeicherte Artikel
📂 Keine gespeicherten Artikel vorhanden.
Zurück Ziehen Vor
Links: vorheriger Artikel Rechts: nächster Artikel unten: schließen
News NIS-2 Frühwarnung Tier-1 Intel ⏱️ 3 Min vor 10 Min
Artikeldaten werden geladen...

Zurück: vorheriger Vor: nächster
↗ Original-Quelle
Social Reaktionen Deine Reaktion zählt
Einstufung & Relevanz-Poll 0 Stimmen
In sozialen Netzwerken teilen 1-Klick