CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/npm-vue--reactivity

Vue.js's standalone reactivity system providing reactive references, objects, computed values, effects, and watchers with fine-grained dependency tracking.

Pending
Overview
Eval results
Files

effects.mddocs/

Effects System

The effects system provides automatic dependency tracking and side effect execution. Effects automatically re-run when their reactive dependencies change, making it easy to create responsive applications.

Capabilities

effect()

Creates a reactive effect that automatically tracks dependencies and re-runs when they change. Returns a runner function that can manually trigger the effect.

/**
 * Creates a reactive effect that tracks dependencies and re-runs on changes
 * @param fn - The effect function to run
 * @param options - Configuration options
 * @returns A runner function that can manually trigger the effect
 */
function effect<T = any>(
  fn: () => T, 
  options?: ReactiveEffectOptions
): ReactiveEffectRunner<T>;

interface ReactiveEffectRunner<T = any> {
  (): T;
  effect: ReactiveEffect;
}

interface ReactiveEffectOptions extends DebuggerOptions {
  scheduler?: EffectScheduler;
  allowRecurse?: boolean;
  onStop?: () => void;
}

type EffectScheduler = (fn: () => void) => void;

Usage Examples:

import { ref, reactive, effect } from "@vue/reactivity";

// Basic effect
const count = ref(0);

const runner = effect(() => {
  console.log(`Count is: ${count.value}`);
});
// Immediately logs: "Count is: 0"

count.value = 1; // Logs: "Count is: 1"
count.value = 2; // Logs: "Count is: 2"

// Manual execution
runner(); // Logs: "Count is: 2"

// Effect with reactive object
const state = reactive({ message: "Hello", count: 0 });

effect(() => {
  console.log(`${state.message} - Count: ${state.count}`);
});
// Logs: "Hello - Count: 0"

state.message = "Hi"; // Logs: "Hi - Count: 0"
state.count = 5; // Logs: "Hi - Count: 5"

// Effect with return value
const user = ref({ name: "Alice", age: 25 });

const logUser = effect(() => {
  const currentUser = user.value;
  console.log(`User: ${currentUser.name}, Age: ${currentUser.age}`);
  return currentUser;
});

user.value = { name: "Bob", age: 30 }; // Logs and returns new user

Effect with Custom Scheduler

Control when effects run by providing a custom scheduler:

import { ref, effect } from "@vue/reactivity";

const count = ref(0);
const updates: (() => void)[] = [];

// Effect with custom scheduler
effect(
  () => {
    console.log(`Count: ${count.value}`);
  },
  {
    scheduler: (fn) => {
      // Batch updates instead of running immediately
      updates.push(fn);
    }
  }
);

// Changes are scheduled, not executed immediately
count.value = 1; // No immediate log
count.value = 2; // No immediate log
count.value = 3; // No immediate log

// Execute batched updates
updates.forEach(fn => fn());
// Logs: "Count: 3" (only once, with final value)

// Async scheduler example
const asyncUpdates: (() => void)[] = [];

effect(
  () => {
    console.log(`Async count: ${count.value}`);
  },
  {
    scheduler: (fn) => {
      asyncUpdates.push(fn);
      Promise.resolve().then(() => {
        const updates = asyncUpdates.splice(0);
        updates.forEach(update => update());
      });
    }
  }
);

stop()

Stops a reactive effect, preventing it from running when dependencies change.

/**
 * Stops a reactive effect
 * @param runner - The effect runner returned by effect()
 */
function stop(runner: ReactiveEffectRunner): void;

Usage Examples:

import { ref, effect, stop } from "@vue/reactivity";

const count = ref(0);

const runner = effect(() => {
  console.log(`Count: ${count.value}`);
});
// Logs: "Count: 0"

count.value = 1; // Logs: "Count: 1"

// Stop the effect
stop(runner);

count.value = 2; // No log (effect stopped)
count.value = 3; // No log (effect stopped)

// Stopped effects can still be manually executed
runner(); // Logs: "Count: 3"

Tracking Control

Control dependency tracking behavior with tracking functions:

/**
 * Temporarily pauses dependency tracking
 */
function pauseTracking(): void;

/**
 * Re-enables effect tracking (if it was paused)
 */
function enableTracking(): void;

/**
 * Resets the previous global effect tracking state
 */
function resetTracking(): void;

Usage Examples:

import { ref, effect, pauseTracking, enableTracking, resetTracking } from "@vue/reactivity";

const count = ref(0);
const name = ref("Alice");

effect(() => {
  console.log(`Count: ${count.value}`);
  
  // Pause tracking temporarily
  pauseTracking();
  
  // This access won't be tracked
  console.log(`Name: ${name.value}`);
  
  // Re-enable tracking
  enableTracking();
});

count.value = 1; // Logs both count and name
name.value = "Bob"; // Does NOT trigger effect (wasn't tracked)

// Reset tracking state
resetTracking();

onEffectCleanup()

Register cleanup functions that run before an effect re-runs or when it's stopped.

/**
 * Registers a cleanup function for the current active effect
 * @param fn - Cleanup function to register
 * @param failSilently - If true, won't warn when no active effect
 */
function onEffectCleanup(fn: () => void, failSilently?: boolean): void;

Usage Examples:

import { ref, effect, onEffectCleanup, stop } from "@vue/reactivity";

const url = ref("https://api.example.com/users");

const runner = effect(() => {
  const controller = new AbortController();
  
  // Register cleanup to cancel request
  onEffectCleanup(() => {
    controller.abort();
    console.log("Request cancelled");
  });
  
  // Make API request
  fetch(url.value, { signal: controller.signal })
    .then(response => response.json())
    .then(data => console.log("Data:", data))
    .catch(error => {
      if (error.name !== "AbortError") {
        console.error("Fetch error:", error);
      }
    });
});

// Changing URL cancels previous request and starts new one
url.value = "https://api.example.com/posts"; // Logs: "Request cancelled"

// Stopping effect also runs cleanup
stop(runner); // Logs: "Request cancelled"

// Timer cleanup example
const interval = ref(1000);

effect(() => {
  const timer = setInterval(() => {
    console.log(`Timer tick (${interval.value}ms)`);
  }, interval.value);
  
  onEffectCleanup(() => {
    clearInterval(timer);
    console.log("Timer cleared");
  });
});

interval.value = 500; // Clears old timer, starts new one

ReactiveEffect Class

The low-level class that powers the effect system. Usually not used directly but available for advanced use cases.

/**
 * Low-level reactive effect class
 */
class ReactiveEffect<T = any> implements Subscriber, ReactiveEffectOptions {
  constructor(public fn: () => T);
  
  /**
   * Pause the effect (stop tracking dependencies)
   */
  pause(): void;
  
  /**
   * Resume the effect (start tracking dependencies)
   */
  resume(): void;
  
  /**
   * Run the effect function and track dependencies
   */
  run(): T;
  
  /**
   * Stop the effect permanently
   */
  stop(): void;
  
  /**
   * Manually trigger the effect
   */
  trigger(): void;
  
  /**
   * Run the effect only if it's dirty (dependencies changed)
   */
  runIfDirty(): void;
  
  /**
   * Check if the effect needs to re-run
   */
  get dirty(): boolean;
}

Usage Examples:

import { ref, ReactiveEffect } from "@vue/reactivity";

const count = ref(0);

// Create effect manually
const effectInstance = new ReactiveEffect(() => {
  console.log(`Manual effect: ${count.value}`);
});

// Must manually run to start tracking
effectInstance.run(); // Logs: "Manual effect: 0"

count.value = 1; // Triggers effect: "Manual effect: 1"

// Pause and resume
effectInstance.pause();
count.value = 2; // No log (paused)

effectInstance.resume();
count.value = 3; // Logs: "Manual effect: 3"

// Check if dirty
console.log(effectInstance.dirty); // false (just ran)
count.value = 4;
console.log(effectInstance.dirty); // true (needs to re-run)

effectInstance.runIfDirty(); // Logs: "Manual effect: 4"

Effect with Allow Recurse

Control whether effects can trigger themselves recursively:

import { ref, effect } from "@vue/reactivity";

const count = ref(0);

// Effect that modifies its own dependency
effect(
  () => {
    console.log(`Count: ${count.value}`);
    
    if (count.value < 5) {
      count.value++; // This would normally cause infinite recursion
    }
  },
  {
    allowRecurse: true // Allow recursive triggering
  }
);

// This will log:
// "Count: 0"
// "Count: 1" 
// "Count: 2"
// "Count: 3"
// "Count: 4"
// "Count: 5"

Effect Debugging

Use debug options to understand effect behavior:

import { ref, effect } from "@vue/reactivity";

const count = ref(0);
const name = ref("Alice");

effect(
  () => {
    console.log(`${name.value}: ${count.value}`);
  },
  {
    onTrack: (event) => {
      console.log("Tracked:", event.key, "on", event.target);
    },
    onTrigger: (event) => {
      console.log("Triggered by:", event.key, "->", event.newValue);
    }
  }
);

// Logs tracking of both dependencies
count.value = 1; // Logs trigger event and effect
name.value = "Bob"; // Logs trigger event and effect

Types

// Core effect types
interface ReactiveEffectRunner<T = any> {
  (): T;
  effect: ReactiveEffect;
}

interface ReactiveEffectOptions extends DebuggerOptions {
  scheduler?: EffectScheduler;
  allowRecurse?: boolean;
  onStop?: () => void;
}

type EffectScheduler = (fn: () => void) => void;

// Effect flags for internal state management
enum EffectFlags {
  ACTIVE = 1 << 0,      // Effect is active
  RUNNING = 1 << 1,     // Effect is currently running
  TRACKING = 1 << 2,    // Effect is tracking dependencies
  NOTIFIED = 1 << 3,    // Effect has been notified of changes
  DIRTY = 1 << 4,       // Effect needs to re-run
  ALLOW_RECURSE = 1 << 5, // Effect can trigger itself
  PAUSED = 1 << 6,      // Effect is paused
  EVALUATED = 1 << 7    // Effect has been evaluated
}

// Debug interfaces
interface DebuggerOptions {
  onTrack?: (event: DebuggerEvent) => void;
  onTrigger?: (event: DebuggerEvent) => void;
}

interface DebuggerEvent {
  effect: ReactiveEffect;
  target: object;
  type: TrackOpTypes | TriggerOpTypes;
  key: any;
  newValue?: any;
  oldValue?: any;
  oldTarget?: Map<any, any> | Set<any>;
}

interface DebuggerEventExtraInfo {
  target: object;
  type: TrackOpTypes | TriggerOpTypes;
  key: any;
  newValue?: any;
  oldValue?: any;
  oldTarget?: Map<any, any> | Set<any>;
}

// Subscriber interface (internal)
interface Subscriber {
  deps: Link[];
  flags: EffectFlags;
  notify(): void;
}

Advanced Patterns

Conditional Effects

Create effects that only track certain dependencies based on conditions:

import { ref, effect, pauseTracking, enableTracking } from "@vue/reactivity";

const mode = ref<"simple" | "advanced">("simple");
const basicCount = ref(0);
const advancedCount = ref(0);

effect(() => {
  console.log(`Mode: ${mode.value}`); // Always tracked
  
  if (mode.value === "simple") {
    console.log(`Basic: ${basicCount.value}`); // Tracked only in simple mode
    
    pauseTracking();
    console.log(`Advanced (not tracked): ${advancedCount.value}`);
    enableTracking();
  } else {
    pauseTracking();
    console.log(`Basic (not tracked): ${basicCount.value}`);
    enableTracking();
    
    console.log(`Advanced: ${advancedCount.value}`); // Tracked only in advanced mode
  }
});

mode.value = "advanced"; // Switches tracking

Effect Composition

Combine multiple effects for complex reactive logic:

import { ref, effect, computed } from "@vue/reactivity";

const items = ref<{ id: number; completed: boolean }[]>([]);
const filter = ref<"all" | "completed" | "pending">("all");

// Base effect for logging changes
effect(() => {
  console.log(`Items updated: ${items.value.length} total`);
});

// Computed for filtered items
const filteredItems = computed(() => {
  switch (filter.value) {
    case "completed":
      return items.value.filter(item => item.completed);
    case "pending":
      return items.value.filter(item => !item.completed);
    default:
      return items.value;
  }
});

// Effect that depends on filtered items
effect(() => {
  console.log(`Showing ${filteredItems.value.length} items (${filter.value})`);
});

// Updates trigger both effects appropriately
items.value = [
  { id: 1, completed: false },
  { id: 2, completed: true }
];

filter.value = "completed"; // Only triggers filter effect

Install with Tessl CLI

npx tessl i tessl/npm-vue--reactivity

docs

computed.md

effect-scopes.md

effects.md

index.md

reactive-objects.md

refs.md

utilities.md

watchers.md

tile.json