-
Notifications
You must be signed in to change notification settings - Fork 0
Signals
The nestix-signal crate provides the reactive runtime used by Nestix. The main
nestix crate re-exports these APIs.
Create mutable reactive state with create_state.
let count = create_state(0);
count.set(1);
count.update(|value| value + 1);
count.mutate(|value| *value += 1);Common methods:
-
borrow(): borrow the current value and track the read. -
get(): clone the current value and track the read. -
set(value): replace the value and notify dependents only if it changed. -
set_unchecked(value): replace and always notify. -
update(|prev| next): compute a replacement from the previous value. -
mutate(|value| ...): mutate in place and notify. -
into_readonly(): expose the state asReadonly<T>.
set requires T: PartialEq. get requires T: Clone.
Use batch to group multiple updates and rerun dependent effects once after the
outermost batch finishes.
batch(|| {
count.set(1);
count.set(2);
});Computed values are still invalidated immediately, so reading a computed value inside the batch returns the current value.
Computed values are lazy, cached derivations of other signals.
let count = create_state(0);
let label = computed!([count] || format!("Count: {}", count.get()));
assert_eq!(label.get(), "Count: 0");The first read runs the closure and caches the result. When any signal read by the closure changes, the computed value is marked dirty. The next read recomputes it.
Use the function form when you do not need capture cloning:
let doubled = nestix::computed(move || count.get() * 2);Use the macro form for ergonomic cloning:
let doubled = computed!([count] || count.get() * 2);Effects run immediately and rerun whenever a signal they read changes.
let count = create_state(0);
effect!([count] || {
log::debug!("count is {}", count.get());
});Each effect refreshes its dependency list every time it runs. If an effect reads different signals after a branch changes, only the latest reads remain tracked.
Keep the returned handle when an effect should be stopped later:
let handle = effect!([count] || {
log::debug!("count is {}", count.get());
});
handle.cancel();Dropping the handle does not cancel the effect.
Use scoped_effect when an effect should automatically stop after an element
unmounts:
scoped_effect(element, closure!([count] || {
log::debug!("count is {}", count.get());
}));The macro form accepts the element first, followed by the same closure syntax as
effect!:
scoped_effect!(element, [count] || {
log::debug!("count is {}", count.get());
});Readonly<T> wraps any signal and exposes only get.
let editable = create_state(String::new());
let readonly = editable.clone().into_readonly();This is useful for list items and child APIs that should react to updates without mutating the original state.
Use untrack when a read should not subscribe the current effect or computed
value.
let snapshot = untrack(|| count.get());Nestix uses this internally when mounting newly created list children so that mounting work does not accidentally become a dependency of the surrounding list effect.
Shared<T> is a small Rc<T> wrapper that compares and hashes by pointer
identity. It is used for callbacks, host handles, dependency sets, and other
values that need stable identity.
let callback: Shared<dyn Fn()> = callback!(|| log::info!("clicked"));Shared<dyn Any> can be downcast:
if let Ok(button) = handle.clone().downcast::<HtmlElement>() {
// use button
}debug_signals configures debug-only checks. In release builds it has no
effect.
debug_signals(DebugConfig {
detect_cyclic: true,
});When detect_cyclic is enabled in debug builds, the runtime logs a warning if
an effect tries to trigger itself while it is already running.