alien-signals

repository·master·Indexed 25 days ago

https://github.com/stackblitz/alien-signals

A high-performance, push-pull based signal library (version 3.2.1) designed with minimal constraints to ensure maximum performance. It provides APIs for reactive state via `signal`, derived state via `computed`, and side effects via `effect`. The library includes `effectScope` for lifecycle management, `startBatch` and `endBatch` for batching updates, and `createReactiveSystem` for building custom Signal APIs using its underlying algorithm.

Tokens
2.3K
Snippets
7
Records
16
Agent score
85%

What's inside alien-signals

  1. Use Basic Signal APIs

    master

    Use signal to create reactive state, computed to create derived state that automatically updates, and effect to run side effects when dependencies change.

    import { signal, computed, effect } from 'alien-signals';
    
    const count = signal(1);
    const doubleCount = computed(() => count() * 2);
    
    effect(() => {
      console.log(`Count is: ${count()}`);
    }); // Console: Count is: 1
    
    console.log(doubleCount()); // 2
    
    count(2); // Console: Count is: 2
    
    console.log(doubleCount()); // 4
  2. Handle Nested Effects

    master

    Effects can be nested. When an outer effect re-runs, any inner effects created during the previous execution are automatically cleaned up. The system ensures outer effects run before inner effects.

    import { signal, effect } from 'alien-signals';
    
    const show = signal(true);
    const count = signal(1);
    
    effect(() => {
      if (show()) {
        // This inner effect is created when show() is true
        effect(() => {
          console.log(`Count is: ${count()}`);
        });
      }
    }); // Console: Count is: 1
    
    count(2); // Console: Count is: 2
    
    // When show becomes false, the inner effect is cleaned up
    show(false); // No output
    
    count(3); // No output (inner effect no longer exists)
  3. Manually Trigger Updates with trigger()

    master

    If you mutate a signal's value directly (e.g., pushing to an array returned by a signal) without using the signal's setter, downstream dependencies will not automatically update. Use trigger() to manually notify dependencies.

    You can trigger a single signal or pass a function to trigger() to notify multiple signals at once.

    import { signal, computed, trigger } from 'alien-signals';
    
    // Single signal trigger
    const arr = signal<number[]>([]);
    const length = computed(() => arr().length);
    
    console.log(length()); // 0
    
    arr().push(1);
    console.log(length()); // Still 0
    
    trigger(arr);
    console.log(length()); // 1
    
    // Multiple signals trigger
    const src1 = signal<number[]>([]);
    const src2 = signal<number[]>([]);
    const total = computed(() => src1().length + src2().length);
    
    src1().push(1);
    src2().push(2);
    
    trigger(() => {
      src1();
      src2();
    });
    
    console.log(total()); // 2
  4. Manage Effects with effectScope

    master

    Use effectScope to group multiple effects together. Calling the function returned by effectScope (the stop function) will clean up all effects created within that scope.

    import { signal, effect, effectScope } from 'alien-signals';
    
    const count = signal(1);
    
    const stopScope = effectScope(() => {
      effect(() => {
        console.log(`Count in scope: ${count()}`);
      }); // Console: Count in scope: 1
    });
    
    count(2); // Console: Count in scope: 2
    
    stopScope();
    
    count(3); // No console output
  5. Create a computed value with computed()

    master
    Use computed<T>(getter) to create a read-only reactive value that derives its state from other signals or computed nodes. The getter function receives the previousValue as an argument. Computed values are lazily evaluated and only re-calculate when their dependencies change.
  6. Batch reactive updates with startBatch() and endBatch()

    master
    To prevent multiple intermediate re-runs of effects during a series of synchronous updates, wrap your updates in startBatch() and endBatch(). This ensures that all changes are processed and effects are flushed only once at the end of the batch.
  7. Initialize a reactive system with createReactiveSystem

    master

    Use createReactiveSystem to instantiate the core reactivity engine. You must provide three callback functions to handle the lifecycle of reactive nodes:

    • update(sub: ReactiveNode): boolean: Called to check if a node needs updating. Returns true if the node is dirty.
    • notify(sub: ReactiveNode): void: Called when a node's state changes and subscribers need to be notified.
    • unwatched(sub: ReactiveNode): void: Called when a node no longer has any dependencies.

    The returned object provides methods for managing links between dependencies and subscribers, propagating changes, and checking for dirty states.

  8. Run a side effect with effect()

    master
    Use effect(fn) to run a function that automatically tracks its reactive dependencies. Whenever any dependency used inside fn changes, the effect will re-run. The function fn can optionally return a cleanup function which is executed before the next run or when the effect is disposed.
  9. Create a signal with signal()

    master
    Use signal<T>(initialValue) to create a reactive primitive that holds a value. A signal can be called as a getter to retrieve the current value, or as a setter to update it. When the value changes, it notifies its subscribers (like effects or computed nodes).