Vue.js's standalone reactivity system providing reactive references, objects, computed values, effects, and watchers with fine-grained dependency tracking.
—
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.
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 userControl 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());
});
}
}
);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"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();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 oneThe 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"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"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// 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;
}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 trackingCombine 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 effectInstall with Tessl CLI
npx tessl i tessl/npm-vue--reactivity