Reactivity¶
Tesserae's reactivity lives in tesserae.reactive and is exported from
tesserae itself.
Signal is the basic reactive cell:
from tesserae import Signal
count = Signal(0)
count.get() # 0 -- records a read-dependency when called during a binding's evaluation
count.set(5) # notifies subscribers, only if the value actually changed
count.update(lambda v: v + 1)
On top of it, derived values, side effects and batched writes:
from tesserae import Computed, Effect, batch
price = Signal(10.0)
quantity = Signal(2)
total = Computed(lambda: price.get() * quantity.get()) # derived, cached
Effect(lambda: print(f"total is now {total.get()}")) # side effect only
def apply_discount():
price.update(lambda p: p * 0.9)
quantity.set(quantity.get() + 1)
batch(apply_discount) # total recomputes once, not twice
Computed(fn)-- a derived, cached value. Recomputes only when one of theSignal/Computedvaluesfnactually reads changes.Effect(fn)-- runsfnonce immediately, then again whenever something it reads changes. Side-effect-only -- has no.get()of its own to read back.batch(fn)-- runsfn, collapsing everySignalwrite inside it into one notification pass at the end, instead of one pass per write. Use it when a single logical action touches severalSignals at once.untrack(fn)-- runsfnwithout recording any read-dependency, even inside aComputed/Effect/binding evaluation currently tracking reads. Use it to read aSignal's current value without making the enclosingComputed/Effectdepend on it.
Computed/Effect duck-type against Signal's own subscribe shape,
so a {{ }} binding in a *_View.yaml can point straight at a
Computed.get() value with no special handling:
bindings: {text: "{{ total.get() }}"}
See Binding Expressions for what a {{ }} expression can contain.
Details worth knowing¶
- Unchanged writes don't notify.
setandupdateskip notification when the new value equals the current one. That's what stops a two-way binding recursing. Computedis eager. It recomputes as soon as a dependency changes, not when next read, and notifies only if its own value changed.- Writing a
Signalwhile it's notifying raises. If a subscriber writes back to the signal that's notifying it, directly or through other signals, you get aRuntimeErrorsaying so rather than endless recursion. Often the fix isuntrack(...)around a read that shouldn't be a dependency. - Use Tesserae's classes.
tesserae.Signaland friends track only each other: a signal from another library read inside atesserae.Computedisn't recorded. - Bindings use them. A bound node updates when a
SignalorComputedits{{ }}expression reads changes. See Binding Expressions.