CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/npm-vue--composition-api

Provides Vue 3 Composition API compatibility for Vue 2 applications with reactive state management and lifecycle hooks.

Pending
Overview
Eval results
Files

watchers.mddocs/

Watchers and Effects

Advanced watching system for tracking reactive data changes with configurable timing, cleanup, and side effects. Watchers enable you to perform side effects in response to reactive state changes.

Capabilities

Watch Function

Watches reactive data sources and executes a callback when they change. Supports watching single sources, multiple sources, and deep watching.

/**
 * Watches a single reactive source
 * @param source - Reactive source to watch
 * @param callback - Callback executed when source changes
 * @param options - Watch configuration options
 * @returns Function to stop watching
 */
function watch<T>(
  source: WatchSource<T>,
  callback: WatchCallback<T>,
  options?: WatchOptions
): WatchStopHandle;

/**
 * Watches multiple reactive sources
 * @param sources - Array of reactive sources to watch
 * @param callback - Callback executed when any source changes
 * @param options - Watch configuration options
 * @returns Function to stop watching
 */
function watch<T extends readonly unknown[]>(
  sources: readonly [...T],
  callback: WatchCallback<MapSources<T>>,
  options?: WatchOptions
): WatchStopHandle;

/**
 * Watches a reactive object with immediate and deep options
 * @param source - Reactive object to watch
 * @param callback - Callback executed when object changes
 * @param options - Watch configuration options
 * @returns Function to stop watching
 */
function watch<T extends object>(
  source: T,
  callback: WatchCallback<T>,
  options?: WatchOptions<true>
): WatchStopHandle;

type WatchSource<T = any> = Ref<T> | ComputedRef<T> | (() => T);
type WatchCallback<T> = (value: T, oldValue: T, onInvalidate: InvalidateCbRegistrator) => void;
type WatchStopHandle = () => void;
type InvalidateCbRegistrator = (fn: () => void) => void;

Usage Examples:

import { ref, reactive, computed, watch } from "@vue/composition-api";

// Watch a single ref
const count = ref(0);
const stopWatching = watch(count, (newValue, oldValue) => {
  console.log(`Count changed from ${oldValue} to ${newValue}`);
});

// Watch multiple sources
const name = ref("Alice");
const age = ref(25);
watch([name, age], ([newName, newAge], [oldName, oldAge]) => {
  console.log(`User changed from ${oldName}(${oldAge}) to ${newName}(${newAge})`);
});

// Watch computed property
const doubleCount = computed(() => count.value * 2);
watch(doubleCount, (newValue) => {
  console.log(`Double count is now: ${newValue}`);
});

// Watch reactive object (deep by default)
const user = reactive({ name: "Bob", profile: { age: 30 } });
watch(user, (newUser, oldUser) => {
  console.log("User object changed:", newUser);
});

// Stop watching when needed
stopWatching();

Watch Options

Configuration options for customizing watch behavior including timing, depth, and immediate execution.

interface WatchOptions<Immediate = boolean> extends WatchOptionsBase {
  immediate?: Immediate;
  deep?: boolean;
}

interface WatchOptionsBase {
  flush?: FlushMode;
}

type FlushMode = "pre" | "post" | "sync";

Watch with Options:

import { ref, watch } from "@vue/composition-api";

const data = ref({ count: 0, nested: { value: 1 } });

// Immediate execution
watch(
  data,
  (newValue, oldValue) => {
    console.log("Data changed:", newValue);
  },
  { immediate: true } // Callback runs immediately with current value
);

// Deep watching (default for objects)
watch(
  data,
  (newValue) => {
    console.log("Deep change detected");
  },
  { deep: true }
);

// Flush timing control
watch(
  data,
  (newValue) => {
    // Runs after DOM updates
    console.log("DOM has been updated");
  },
  { flush: "post" }
);

watch(
  data,
  (newValue) => {
    // Runs before DOM updates (default)
    console.log("Before DOM update");
  },
  { flush: "pre" }
);

watch(
  data,
  (newValue) => {
    // Runs synchronously
    console.log("Sync execution");
  },
  { flush: "sync" }
);

Watch Effects

Runs an effect function immediately and re-runs it whenever its reactive dependencies change. Unlike watch, watchEffect doesn't need explicit sources.

/**
 * Runs effect immediately and re-runs when dependencies change
 * @param effect - Effect function to run
 * @returns Function to stop the effect
 */
function watchEffect(effect: WatchEffect): WatchStopHandle;

/**
 * Post-flush watchEffect (runs after DOM updates)
 * @param effect - Effect function to run
 * @returns Function to stop the effect
 */
function watchPostEffect(effect: WatchEffect): WatchStopHandle;

/**
 * Sync watchEffect (runs synchronously)
 * @param effect - Effect function to run
 * @returns Function to stop the effect
 */
function watchSyncEffect(effect: WatchEffect): WatchStopHandle;

type WatchEffect = (onInvalidate: InvalidateCbRegistrator) => void;

Usage Examples:

import { ref, reactive, watchEffect, watchPostEffect } from "@vue/composition-api";

const count = ref(0);
const user = reactive({ name: "Alice", age: 25 });

// Basic watchEffect - automatically tracks dependencies
const stop = watchEffect(() => {
  console.log(`Count is ${count.value}, user is ${user.name}`);
  // This effect will re-run when count or user.name changes
});

// Effect with cleanup
watchEffect(async (onInvalidate) => {
  const controller = new AbortController();
  
  // Register cleanup function
  onInvalidate(() => {
    controller.abort();
  });
  
  try {
    const response = await fetch(`/api/user/${user.name}`, {
      signal: controller.signal,
    });
    const userData = await response.json();
    console.log("User data:", userData);
  } catch (error) {
    if (error.name !== "AbortError") {
      console.error("Failed to fetch user data:", error);
    }
  }
});

// Post-flush effect (runs after DOM updates)
watchPostEffect(() => {
  // Access DOM elements after updates
  const element = document.getElementById("counter");
  if (element) {
    element.textContent = count.value.toString();
  }
});

// Stop the effect when needed
stop();

Advanced Watching Patterns

Complex watching scenarios including conditional watching, async effects, and performance optimization.

Conditional Watching:

import { ref, computed, watch } from "@vue/composition-api";

const isEnabled = ref(false);
const data = ref("initial");

// Only watch when condition is met
const conditionalStop = watch(
  data,
  (newValue) => {
    if (isEnabled.value) {
      console.log("Data changed:", newValue);
    }
  }
);

// Alternative: Dynamic watcher creation
let stopWatcher: (() => void) | null = null;

watch(isEnabled, (enabled) => {
  if (enabled && !stopWatcher) {
    stopWatcher = watch(data, (newValue) => {
      console.log("Conditional watch:", newValue);
    });
  } else if (!enabled && stopWatcher) {
    stopWatcher();
    stopWatcher = null;
  }
});

Debounced Watching:

import { ref, watch } from "@vue/composition-api";

const searchTerm = ref("");

// Debounced search
watch(
  searchTerm,
  (newTerm, oldTerm, onInvalidate) => {
    const timeout = setTimeout(() => {
      console.log("Searching for:", newTerm);
      // Perform search API call
    }, 300);
    
    // Cleanup previous timeout
    onInvalidate(() => {
      clearTimeout(timeout);
    });
  }
);

Watching Getters with Dependencies:

import { ref, watch } from "@vue/composition-api";

const user = ref({ id: 1, name: "Alice" });
const posts = ref<any[]>([]);

// Watch a computed getter
watch(
  () => user.value.id,
  async (userId, prevUserId, onInvalidate) => {
    const controller = new AbortController();
    onInvalidate(() => controller.abort());
    
    try {
      const response = await fetch(`/api/users/${userId}/posts`, {
        signal: controller.signal,
      });
      posts.value = await response.json();
    } catch (error) {
      if (error.name !== "AbortError") {
        console.error("Failed to fetch posts:", error);
      }
    }
  },
  { immediate: true }
);

Watch in Component Setup

Typical usage patterns within Vue component setup functions with automatic cleanup.

import { defineComponent, ref, watch, onUnmounted } from "@vue/composition-api";

export default defineComponent({
  setup() {
    const query = ref("");
    const results = ref([]);
    const loading = ref(false);
    
    // Watchers are automatically cleaned up when component unmounts
    watch(
      query,
      async (newQuery, oldQuery, onInvalidate) => {
        if (!newQuery.trim()) {
          results.value = [];
          return;
        }
        
        loading.value = true;
        const controller = new AbortController();
        onInvalidate(() => {
          controller.abort();
          loading.value = false;
        });
        
        try {
          const response = await fetch(`/api/search?q=${encodeURIComponent(newQuery)}`, {
            signal: controller.signal,
          });
          const data = await response.json();
          results.value = data.results;
        } catch (error) {
          if (error.name !== "AbortError") {
            console.error("Search failed:", error);
            results.value = [];
          }
        } finally {
          loading.value = false;
        }
      },
      { immediate: false }
    );
    
    return {
      query,
      results,
      loading,
    };
  },
});

Types

type WatchSource<T = any> = Ref<T> | ComputedRef<T> | (() => T);

type WatchCallback<V = any, OV = any> = (
  value: V,
  oldValue: OV,
  onInvalidate: InvalidateCbRegistrator
) => any;

type WatchEffect = (onInvalidate: InvalidateCbRegistrator) => void;

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

type WatchStopHandle = () => void;

type MapSources<T, Immediate> = {
  [K in keyof T]: T[K] extends WatchSource<infer V>
    ? Immediate extends true
      ? V | undefined
      : V
    : never;
};

type MultiWatchSources = (WatchSource<unknown> | object)[];

interface WatchOptions<Immediate = boolean> extends WatchOptionsBase {
  immediate?: Immediate;
  deep?: boolean;
}

interface WatchOptionsBase {
  flush?: FlushMode;
}

type FlushMode = "pre" | "post" | "sync";

interface VueWatcher {
  lazy: boolean;
  get(): any;
  teardown(): void;
  update(): void;
  run(): void;
  evaluate(): void;
  depend(): void;
}

Install with Tessl CLI

npx tessl i tessl/npm-vue--composition-api

docs

advanced-features.md

component-utilities.md

computed.md

dependency-injection.md

index.md

lifecycle.md

reactive-state.md

types.md

watchers.md

tile.json