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

utilities.mddocs/

Utility Functions

Utility functions provide additional tools for working with reactive values, type conversions, and advanced reactive operations that don't fit into the core reactive categories.

Capabilities

toRef()

Normalizes values/refs/getters into refs, or creates a ref for a property on a reactive object.

/**
 * Converts a value to a ref
 * @param value - Value to convert to ref
 * @returns A ref containing the value
 */
function toRef<T>(value: T): ToRef<T>;

/**
 * Creates a ref for a property on a reactive object
 * @param object - Reactive object
 * @param key - Property key
 * @returns A ref linked to the object property
 */
function toRef<T extends object, K extends keyof T>(object: T, key: K): ToRef<T[K]>;

/**
 * Creates a ref for a property with a default value
 * @param object - Reactive object
 * @param key - Property key  
 * @param defaultValue - Default value if property is undefined
 * @returns A ref linked to the object property with default
 */
function toRef<T extends object, K extends keyof T>(
  object: T, 
  key: K, 
  defaultValue: T[K]
): ToRef<Exclude<T[K], undefined>>;

Usage Examples:

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

const state = reactive({ 
  count: 0, 
  user: { name: "Alice" },
  tags: ["vue", "reactivity"]
});

// Create refs from reactive object properties
const countRef = toRef(state, "count");
const userRef = toRef(state, "user");

// Refs maintain connection to original object
effect(() => {
  console.log(`Count ref: ${countRef.value}`);
});

countRef.value = 5; // Updates state.count and triggers effect
console.log(state.count); // 5

// Convert plain value to ref
const messageRef = toRef("Hello World");
console.log(messageRef.value); // "Hello World"

// Property ref with default value
const unknownProperty = toRef(state, "missing" as any, "default");
console.log(unknownProperty.value); // "default"

toRefs()

Converts a reactive object to a plain object where each property is a ref that maintains reactivity connection to the original object.

/**
 * Converts a reactive object to refs for each property
 * @param object - Reactive object to convert
 * @returns Object with ref properties
 */
function toRefs<T extends object>(object: T): ToRefs<T>;

type ToRefs<T = any> = {
  [K in keyof T]: ToRef<T[K]>;
};

Usage Examples:

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

const state = reactive({
  count: 0,
  message: "Hello",
  user: { name: "Alice" },
  items: [1, 2, 3]
});

// Convert to refs - useful for destructuring without losing reactivity
const { count, message, user, items } = toRefs(state);

// All refs maintain connection to original state
effect(() => {
  console.log(`${message.value}: ${count.value}`);
});

count.value = 10; // Updates state.count and triggers effect
message.value = "Count"; // Updates state.message and triggers effect

// Useful in composition functions
function useCounter(initialValue = 0) {
  const state = reactive({
    count: initialValue,
    doubled: computed(() => state.count * 2)
  });
  
  const increment = () => state.count++;
  const decrement = () => state.count--;
  
  // Return refs for easy destructuring
  return {
    ...toRefs(state),
    increment,
    decrement
  };
}

const { count: myCount, doubled } = useCounter(5);
console.log(myCount.value); // 5
console.log(doubled.value); // 10

toValue()

Normalizes values/refs/getters to values. Similar to unref but also handles getter functions.

/**
 * Normalizes values/refs/getters to values
 * @param source - A ref, getter function, or plain value  
 * @returns The resolved value
 */
function toValue<T>(source: MaybeRefOrGetter<T>): T;

type MaybeRefOrGetter<T = any> = MaybeRef<T> | ComputedRef<T> | (() => T);

Usage Examples:

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

const count = ref(42);
const doubled = computed(() => count.value * 2);
const getValue = () => 100;
const plainValue = 200;

// Works with all types
console.log(toValue(count)); // 42
console.log(toValue(doubled)); // 84  
console.log(toValue(getValue)); // 100
console.log(toValue(plainValue)); // 200

// Useful for flexible function parameters
function useFlexibleValue<T>(source: MaybeRefOrGetter<T>) {
  const value = toValue(source);
  console.log("Current value:", value);
  return value;
}

useFlexibleValue(count); // Works with ref
useFlexibleValue(doubled); // Works with computed
useFlexibleValue(getValue); // Works with getter
useFlexibleValue(300); // Works with plain value

markRaw()

Marks an object so that it will never be converted to a proxy. Useful for performance optimization or compatibility with third-party libraries.

/**
 * Marks an object to never be converted to a proxy
 * @param value - Object to mark as raw
 * @returns The same object with a skip marker
 */
function markRaw<T extends object>(value: T): Raw<T>;

type Raw<T> = T & { [RawSymbol]?: true };

Usage Examples:

import { reactive, markRaw, isReactive } from "@vue/reactivity";

// Large dataset that doesn't need reactivity
const largeDataSet = markRaw(new Array(10000).fill(0).map((_, i) => ({ id: i })));

// Third-party library instance
const thirdPartyLib = markRaw(new SomeLibrary());

// Date objects (often don't need reactivity)
const timestamp = markRaw(new Date());

const state = reactive({
  count: 0,
  data: largeDataSet,        // Won't be made reactive
  lib: thirdPartyLib,        // Won't be made reactive  
  created: timestamp,        // Won't be made reactive
  config: { theme: "dark" }  // Will be made reactive
});

console.log(isReactive(state.data)); // false
console.log(isReactive(state.lib)); // false
console.log(isReactive(state.created)); // false
console.log(isReactive(state.config)); // true

// Performance benefit: mutations to marked raw objects don't trigger effects
state.data.push({ id: 10000 }); // No reactive overhead

// Map and Set examples
const nonReactiveMap = markRaw(new Map([
  ["key1", "value1"],
  ["key2", "value2"]
]));

const reactiveState = reactive({
  lookup: nonReactiveMap // Map operations won't trigger reactivity
});

// Direct Map operations are fast and don't trigger effects
reactiveState.lookup.set("key3", "value3");
reactiveState.lookup.delete("key1");

customRef()

Creates a customized ref with explicit control over dependency tracking and updates.

/**
 * Creates a customized ref with explicit control over tracking and triggering
 * @param factory - Factory function that receives track and trigger callbacks
 * @returns A custom ref object
 */
function customRef<T>(factory: CustomRefFactory<T>): Ref<T>;

type CustomRefFactory<T> = (
  track: () => void,
  trigger: () => void
) => {
  get: () => T;
  set: (value: T) => void;
};

Usage Examples:

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

// Debounced ref that delays updates
function useDebouncedRef<T>(value: T, delay = 200) {
  let timeout: number;
  
  return customRef<T>((track, trigger) => {
    return {
      get() {
        track(); // Track dependency
        return value;
      },
      set(newValue: T) {
        clearTimeout(timeout);
        timeout = setTimeout(() => {
          value = newValue;
          trigger(); // Trigger updates after delay
        }, delay);
      }
    };
  });
}

const debouncedValue = useDebouncedRef("initial", 300);

effect(() => {
  console.log("Debounced:", debouncedValue.value);
});
// Immediately logs: "Debounced: initial"

debouncedValue.value = "change1";
debouncedValue.value = "change2";
debouncedValue.value = "final";
// After 300ms delay, logs: "Debounced: final"

// Validated ref that only accepts valid values
function useValidatedRef<T>(
  initialValue: T,
  validator: (value: T) => boolean
) {
  let _value = initialValue;
  
  return customRef<T>((track, trigger) => {
    return {
      get() {
        track();
        return _value;
      },
      set(newValue: T) {
        if (validator(newValue)) {
          _value = newValue;
          trigger();
        } else {
          console.warn("Invalid value rejected:", newValue);
        }
      }
    };
  });
}

const positiveNumber = useValidatedRef(1, (value: number) => value > 0);

effect(() => {
  console.log("Valid number:", positiveNumber.value);
});
// Logs: "Valid number: 1"

positiveNumber.value = 5; // Logs: "Valid number: 5"
positiveNumber.value = -1; // Warns and doesn't update
positiveNumber.value = 10; // Logs: "Valid number: 10"

// Async ref that tracks loading state
function useAsyncRef<T>(asyncFn: () => Promise<T>, initialValue: T) {
  let _value = initialValue;
  let _loading = false;
  let _error: Error | null = null;
  
  const valueRef = customRef<T>((track, trigger) => ({
    get() {
      track();
      return _value;
    },
    set(newValue: T) {
      _value = newValue;
      trigger();
    }
  }));
  
  const loadingRef = customRef<boolean>((track, trigger) => ({
    get() {
      track();
      return _loading;
    },
    set(newValue: boolean) {
      _loading = newValue;
      trigger();
    }
  }));
  
  const load = async () => {
    loadingRef.value = true;
    _error = null;
    
    try {
      const result = await asyncFn();
      valueRef.value = result;
    } catch (error) {
      _error = error as Error;
    } finally {
      loadingRef.value = false;
    }
  };
  
  return {
    value: valueRef,
    loading: loadingRef,
    error: () => _error,
    load
  };
}

proxyRefs()

Returns a proxy that shallowly unwraps properties that are refs, allowing direct property access without .value.

/**
 * Creates a proxy that automatically unwraps ref properties
 * @param objectWithRefs - Object containing ref properties
 * @returns Proxy with automatic ref unwrapping
 */
function proxyRefs<T extends object>(objectWithRefs: T): ShallowUnwrapRef<T>;

type ShallowUnwrapRef<T> = {
  [K in keyof T]: DistributeRef<T[K]>;
};

Usage Examples:

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

// Object with mixed refs and plain values
const refs = {
  count: ref(0),
  message: ref("Hello"),
  doubled: computed(() => refs.count.value * 2),
  config: { theme: "dark" }, // Plain object
  items: ref([1, 2, 3])
};

// Create proxy that unwraps refs
const proxy = proxyRefs(refs);

// Access without .value
console.log(proxy.count); // 0 (unwrapped from ref)
console.log(proxy.message); // "Hello" (unwrapped from ref)
console.log(proxy.doubled); // 0 (unwrapped from computed)
console.log(proxy.config); // { theme: "dark" } (plain value)

// Assignment unwraps refs automatically
proxy.count = 5; // Same as refs.count.value = 5
proxy.message = "Hi"; // Same as refs.message.value = "Hi"

// Effects work with the proxy
effect(() => {
  console.log(`${proxy.message}: ${proxy.count} (doubled: ${proxy.doubled})`);
});
// Logs: "Hi: 5 (doubled: 10)"

proxy.count = 10; // Logs: "Hi: 10 (doubled: 20)"

// Useful for component setup return
function useComponent() {
  const state = reactive({
    loading: false,
    data: null
  });
  
  const count = ref(0);
  const doubled = computed(() => count.value * 2);
  
  // Return proxy for easy template access
  return proxyRefs({
    ...toRefs(state),
    count,
    doubled,
    increment: () => count.value++
  });
}

const component = useComponent();
// Template can use: component.loading, component.count, etc.

Type Checking and Conversion Utilities

Additional utilities for working with reactive types and checking proxy states:

/**
 * Checks if a value is a proxy created by reactive(), readonly(), shallowReactive() or shallowReadonly()
 * @param value - Value to check
 * @returns True if the value is a proxy
 */
function isProxy(value: any): boolean;

/**
 * Checks if a value is a shallow reactive proxy
 * @param value - Value to check
 * @returns True if the value is shallow
 */
function isShallow(value: unknown): boolean;

/**
 * Returns a reactive proxy if the value is an object, otherwise the value itself
 * @param value - Value to potentially make reactive
 * @returns Reactive proxy or original value
 */
function toReactive<T>(value: T): T;

/**
 * Returns a readonly proxy if the value is an object, otherwise the value itself  
 * @param value - Value to potentially make readonly
 * @returns Readonly proxy or original value
 */
function toReadonly<T>(value: T): T;

Usage Examples:

import { 
  reactive, readonly, shallowReactive, shallowReadonly,
  isProxy, isShallow, isReactive, isReadonly,
  toReactive, toReadonly, markRaw
} from "@vue/reactivity";

// Type checking utilities
const plainObj = { count: 0 };
const reactiveObj = reactive(plainObj);
const readonlyObj = readonly(plainObj);
const shallowObj = shallowReactive(plainObj);
const rawObj = markRaw({ data: [] });

console.log(isProxy(plainObj)); // false
console.log(isProxy(reactiveObj)); // true
console.log(isProxy(readonlyObj)); // true
console.log(isProxy(shallowObj)); // true
console.log(isProxy(rawObj)); // false

console.log(isShallow(reactiveObj)); // false (deep reactive)
console.log(isShallow(shallowObj)); // true
console.log(isShallow(readonlyObj)); // false

// toReactive converts objects to reactive, leaves primitives alone
const reactiveConverted = toReactive({ count: 0 }); // Becomes reactive
const reactiveNum = toReactive(42); // Stays 42
const reactiveStr = toReactive("hello"); // Stays "hello"

console.log(isReactive(reactiveConverted)); // true
console.log(isReactive(reactiveNum)); // false
console.log(isReactive(reactiveStr)); // false

// toReadonly converts objects to readonly, leaves primitives alone  
const readonlyConverted = toReadonly({ count: 0 }); // Becomes readonly
const readonlyNum = toReadonly(42); // Stays 42

console.log(isReadonly(readonlyConverted)); // true
console.log(isReadonly(readonlyNum)); // false

// Useful for conditional reactivity
function makeReactiveIf<T>(value: T, condition: boolean): T {
  return condition ? toReactive(value) : value;
}

const conditionallyReactive = makeReactiveIf({ data: [] }, true);

// Utility for checking proxy types
function getProxyType(value: any): string {
  if (!isProxy(value)) return 'not-proxy';
  if (isReadonly(value)) return isShallow(value) ? 'shallow-readonly' : 'readonly';
  if (isReactive(value)) return isShallow(value) ? 'shallow-reactive' : 'reactive';
  return 'unknown-proxy';
}

console.log(getProxyType(plainObj)); // 'not-proxy'
console.log(getProxyType(reactiveObj)); // 'reactive'
console.log(getProxyType(readonlyObj)); // 'readonly'
console.log(getProxyType(shallowObj)); // 'shallow-reactive'

Advanced Utility Patterns

Flexible Parameter Handling

Create utilities that work with different input types:

import { toValue, MaybeRefOrGetter } from "@vue/reactivity";

function useFormatter<T>(
  getValue: MaybeRefOrGetter<T>,
  formatter: (value: T) => string
) {
  return computed(() => {
    const value = toValue(getValue);
    return formatter(value);
  });
}

// Works with refs, getters, or plain values
const count = ref(42);
const formatted1 = useFormatter(count, (n) => `Count: ${n}`);
const formatted2 = useFormatter(() => count.value * 2, (n) => `Double: ${n}`);
const formatted3 = useFormatter(100, (n) => `Static: ${n}`);

Reactive Object Composition

Combine utilities for complex reactive patterns:

import { reactive, toRefs, markRaw, customRef } from "@vue/reactivity";

function useStore<T extends object>(initialData: T) {
  // Core reactive state
  const state = reactive({ ...initialData });
  
  // Non-reactive metadata
  const meta = markRaw({
    created: new Date(),
    version: "1.0.0"
  });
  
  // Custom refs for special behavior
  const lastUpdated = customRef<Date>((track, trigger) => {
    let value = new Date();
    return {
      get() {
        track();
        return value;
      },
      set() {
        value = new Date();
        trigger();
      }
    };
  });
  
  // Update function that triggers lastUpdated
  const updateState = (updates: Partial<T>) => {
    Object.assign(state, updates);
    lastUpdated.value = new Date(); // Triggers the custom ref
  };
  
  return {
    ...toRefs(state), // Individual property refs
    state,            // Full reactive state
    meta,             // Non-reactive metadata
    lastUpdated,      // Custom ref
    updateState       // Update function
  };
}

const store = useStore({
  count: 0,
  message: "Hello"
});

// Use individual refs
store.count.value = 5;

// Or update via function
store.updateState({ message: "Updated" });

console.log(store.lastUpdated.value); // Shows update time

Types

// Conversion types
type ToRef<T> = IfAny<T, Ref<T>, [T] extends [Ref] ? T : Ref<T>>;
type ToRefs<T = any> = { [K in keyof T]: ToRef<T[K]> };

// Utility types
type MaybeRef<T = any> = T | Ref<T> | ShallowRef<T> | WritableComputedRef<T>;
type MaybeRefOrGetter<T = any> = MaybeRef<T> | ComputedRef<T> | (() => T);

// Unwrapping types
type ShallowUnwrapRef<T> = { [K in keyof T]: DistributeRef<T[K]> };
type DistributeRef<T> = T extends Ref<infer V> ? V : T;

// Raw type
type Raw<T> = T & { [RawSymbol]?: true };

// Custom ref factory
type CustomRefFactory<T> = (
  track: () => void,
  trigger: () => void
) => {
  get: () => T;
  set: (value: T) => void;
};

// Conditional type helpers
type IfAny<T, Y, N> = 0 extends 1 & T ? Y : N;

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