Crate reaktiv

Crate reaktiv 

Source
Expand description

Standalone, flexible fine-grained reactivity

This crate brings the benefits of fine-grained reactivity to contexts beyond traditional web UI. Signals are lightweight metadata (4 bytes) - your values stay in your structs.

§Quick Start

use reaktiv::{Signal, Effect, Computed, Transaction, flush_effects};

struct Component {
    value: f64,
    signal: Signal,  // Just 4 bytes of metadata
}

impl Component {
    fn set(&mut self, v: f64) {
        self.value = v;
        self.signal.emit();  // Notify subscribers
    }

    fn get(&self) -> f64 {
        self.signal.track_dependency();  // Auto-track in effects
        self.value
    }
}

// Effects auto-track dependencies and run immediately
let effect = Effect::new(|| {
    let value = component.get();  // Tracks dependency automatically
    println!("Value: {}", value);
});

// Batch changes with transactions - effects run once at the end
Transaction::run(|| {
    component.set(1.0);
    component.set(2.0);
});  // Effect runs once with final value

flush_effects();

§Core Types

  • Signal - Lightweight reactive marker (4 bytes). Call emit() when value changes.
  • Effect - Auto-runs when tracked signals change. Runs immediately on creation.
  • Computed<T> - Cached derived value. Only recomputes when dependencies change.
  • Transaction - Batch multiple changes into one effect run.

§Working with Signals

let signal = Signal::new();

// Inside an effect, track this signal as a dependency
signal.track_dependency();

// Notify all subscribers when value changes
signal.emit();

// Skip UI-only subscribers (useful for preventing update loops)
signal.emit_from_ui();

§Creating Effects

// Effect runs immediately and tracks dependencies automatically
let effect = Effect::new(|| {
    signal.track_dependency();
    println!("Signal changed!");
});

// Skippable effects can be deferred under load
let skippable = Effect::new_skippable(|| {
    // Non-critical UI updates
});

§Computed Values

// Runs immediately and caches the result
let doubled = Computed::new(|| {
    input_signal.track_dependency();
    input_value * 2
});

// Defers computation until first access
let lazy = Computed::lazy(|| expensive_computation());

let value = doubled.get();  // Returns cached value

§Batching Updates

// Effects run once at the end instead of 3 times
Transaction::run(|| {
    signal_a.emit();
    signal_b.emit();
    signal_c.emit();
});  // All pending effects run here

// Suppress UI-triggered effects during internal updates
Transaction::no_ui_updates(|| {
    internal_recalculation();
});

§Processing Effects

flush_effects();              // Process all pending effects synchronously
is_processing_scheduled();    // Check if effects are waiting to run
untracked(|| { ... });        // Read signals without tracking dependencies

// For async/event loop integration
spawn_effect_loop();          // Start async effect processing

Structs§

Computed
Memoized derived value that automatically updates when dependencies change
Effect
Side-effectful computation that automatically re-runs when dependencies change
EffectLoop
Builder for configuring and spawning the effect processing loop.
Signal
Lightweight reactive marker that tracks dependencies without owning data
Transaction
Batch multiple signal changes into a single effect run

Constants§

DEFAULT_BUDGET
Default time budget for effect processing (~60fps).
DEFAULT_DEBOUNCE
Default debounce delay for effect processing.
DEFAULT_MAX_DEBOUNCE
Maximum wait time for debouncing.

Functions§

flush_effects
Process all pending effects immediately
is_processing_scheduled
Check if effect processing is scheduled
spawn_effect_loop
Spawn the effect processing loop with default settings.
untracked
Run a closure without tracking dependencies