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

reactive-objects.mddocs/

Reactive Objects

Reactive objects provide deep reactivity tracking for complex data structures using JavaScript Proxies. They automatically track property access and mutations, enabling fine-grained reactivity for objects, arrays, Maps, Sets, and other collections.

Capabilities

reactive()

Creates a reactive proxy of an object with deep reactive conversion. All nested objects and arrays become reactive, and refs are automatically unwrapped.

/**
 * Creates a reactive proxy with deep reactive conversion
 * @param target - Object to make reactive
 * @returns Reactive proxy that tracks all property access and changes
 */
function reactive<T extends object>(target: T): Reactive<T>;

type Reactive<T> = UnwrapNestedRefs<T> & (T extends readonly any[] ? ReactiveMarker : {});

Usage Examples:

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

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

effect(() => {
  console.log(`Count: ${state.count}, Message: ${state.message}`);
});

state.count++; // Triggers effect
state.message = "Hello World"; // Triggers effect

// Nested objects are automatically reactive
const user = reactive({
  profile: {
    name: "Alice",
    settings: {
      theme: "dark"
    }
  },
  posts: []
});

// All levels are reactive
user.profile.name = "Bob"; // Triggers effects
user.posts.push({ title: "New Post" }); // Triggers effects
user.profile.settings.theme = "light"; // Triggers effects

// Arrays are reactive
const items = reactive([1, 2, 3]);
items.push(4); // Triggers effects
items[0] = 10; // Triggers effects

readonly()

Creates a readonly proxy to the original object with deep readonly conversion. The proxy has the same ref-unwrapping behavior as reactive objects but prevents mutations.

/**
 * Creates a readonly proxy with deep readonly conversion
 * @param target - Object to make readonly
 * @returns Deep readonly proxy that prevents mutations
 */
function readonly<T extends object>(target: T): DeepReadonly<UnwrapNestedRefs<T>>;

type DeepReadonly<T> = {
  readonly [P in keyof T]: T[P] extends object ? DeepReadonly<T[P]> : T[P];
};

Usage Examples:

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

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

const readonlyState = readonly(original);

// Reading works fine
console.log(readonlyState.count); // 0
console.log(readonlyState.user.name); // "Alice"

// Mutations will warn in development and be ignored
// readonlyState.count = 1; // Warning in dev, ignored
// readonlyState.user.name = "Bob"; // Warning in dev, ignored

// Original can still be mutated
original.count = 5; // This works and readonlyState reflects the change
console.log(readonlyState.count); // 5

shallowReactive()

Creates a shallow reactive proxy where only root-level properties are reactive. Nested objects are not converted to reactive.

/**
 * Creates a shallow reactive proxy - only root level properties are reactive
 * @param target - Object to make shallow reactive
 * @returns Shallow reactive proxy
 */
function shallowReactive<T extends object>(target: T): ShallowReactive<T>;

type ShallowReactive<T> = T & ReactiveMarker;

Usage Examples:

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

const state = shallowReactive({
  count: 0,
  user: {
    name: "Alice" // This nested object is NOT reactive
  }
});

effect(() => {
  console.log(`Count: ${state.count}`);
});

effect(() => {
  console.log(`User name: ${state.user.name}`);
});

state.count++; // Triggers first effect
state.user.name = "Bob"; // Does NOT trigger second effect (shallow)

// Replacing the entire object triggers effects
state.user = { name: "Charlie" }; // Triggers second effect

shallowReadonly()

Creates a shallow readonly proxy where only root-level properties are readonly. Nested objects can still be mutated.

/**
 * Creates a shallow readonly proxy - only root level properties are readonly
 * @param target - Object to make shallow readonly
 * @returns Shallow readonly proxy
 */
function shallowReadonly<T extends object>(target: T): Readonly<T>;

Usage Examples:

import { shallowReadonly } from "@vue/reactivity";

const state = {
  count: 0,
  user: {
    name: "Alice"
  }
};

const readonlyState = shallowReadonly(state);

// Root level mutations are blocked
// readonlyState.count = 1; // Warning in dev, ignored

// But nested mutations work (shallow)
readonlyState.user.name = "Bob"; // This works!
console.log(readonlyState.user.name); // "Bob"

isReactive()

Checks if an object is a proxy created by reactive() or shallowReactive().

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

Usage Examples:

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

const reactiveObj = reactive({ count: 0 });
const shallowObj = shallowReactive({ count: 0 });
const readonlyObj = readonly({ count: 0 });
const plainObj = { count: 0 };

console.log(isReactive(reactiveObj)); // true
console.log(isReactive(shallowObj)); // true
console.log(isReactive(readonlyObj)); // false (readonly, not reactive)
console.log(isReactive(plainObj)); // false

isReadonly()

Checks if an object is a readonly proxy created by readonly() or shallowReadonly().

/**
 * Checks if an object is a readonly proxy
 * @param value - Value to check
 * @returns True if the value is a readonly proxy
 */
function isReadonly(value: unknown): boolean;

Usage Examples:

import { reactive, readonly, shallowReadonly, isReadonly } from "@vue/reactivity";

const reactiveObj = reactive({ count: 0 });
const readonlyObj = readonly({ count: 0 });
const shallowReadonlyObj = shallowReadonly({ count: 0 });
const plainObj = { count: 0 };

console.log(isReadonly(reactiveObj)); // false
console.log(isReadonly(readonlyObj)); // true
console.log(isReadonly(shallowReadonlyObj)); // true
console.log(isReadonly(plainObj)); // false

isShallow()

Checks if an object is a shallow reactive or readonly proxy.

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

Usage Examples:

import { reactive, shallowReactive, readonly, shallowReadonly, isShallow } from "@vue/reactivity";

const reactiveObj = reactive({ count: 0 });
const shallowReactiveObj = shallowReactive({ count: 0 });
const readonlyObj = readonly({ count: 0 });
const shallowReadonlyObj = shallowReadonly({ count: 0 });

console.log(isShallow(reactiveObj)); // false
console.log(isShallow(shallowReactiveObj)); // true
console.log(isShallow(readonlyObj)); // false
console.log(isShallow(shallowReadonlyObj)); // true

isProxy()

Checks if an object is any type of Vue-created proxy (reactive, readonly, shallow reactive, or shallow readonly).

/**
 * Checks if an object is any type of Vue proxy
 * @param value - Value to check
 * @returns True if the value is any type of Vue proxy
 */
function isProxy(value: any): boolean;

Usage Examples:

import { reactive, readonly, shallowReactive, isProxy } from "@vue/reactivity";

const reactiveObj = reactive({ count: 0 });
const readonlyObj = readonly({ count: 0 });
const shallowObj = shallowReactive({ count: 0 });
const plainObj = { count: 0 };

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

toRaw()

Returns the raw, original object of a Vue-created proxy. Useful for getting the original object without proxy behavior.

/**
 * Returns the raw original object of a Vue-created proxy
 * @param observed - A Vue proxy object
 * @returns The original object without proxy wrapper
 */
function toRaw<T>(observed: T): T;

Usage Examples:

import { reactive, readonly, toRaw } from "@vue/reactivity";

const original = { count: 0, user: { name: "Alice" } };
const reactiveObj = reactive(original);
const readonlyObj = readonly(reactiveObj);

console.log(toRaw(reactiveObj) === original); // true
console.log(toRaw(readonlyObj) === original); // true

// Useful for passing non-reactive objects to third-party APIs
function saveToAPI(data: any) {
  // Send raw object without reactivity
  fetch("/api/save", {
    method: "POST",
    body: JSON.stringify(toRaw(data))
  });
}

saveToAPI(reactiveObj); // Sends original object

markRaw()

Marks an object so that it will never be converted to a proxy. Useful for objects that should remain non-reactive for performance or compatibility reasons.

/**
 * 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 } from "@vue/reactivity";

// Mark an object to prevent reactivity
const nonReactiveObj = markRaw({
  largeDataSet: new Array(10000).fill(0),
  thirdPartyInstance: new SomeLibrary()
});

const state = reactive({
  count: 0,
  data: nonReactiveObj // This won't be made reactive
});

// state.count is reactive, but state.data is not
state.count++; // Triggers effects
state.data.largeDataSet.push(1); // Does NOT trigger effects (marked raw)

// Useful for large objects or third-party instances
const map = markRaw(new Map());
const reactiveState = reactive({
  myMap: map // Map stays non-reactive for performance
});

toReactive()

Utility function that returns a reactive proxy if the value is an object, otherwise returns the value itself.

/**
 * 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
 */
const toReactive: <T extends unknown>(value: T) => T;

Usage Examples:

import { toReactive } from "@vue/reactivity";

const obj = { count: 0 };
const num = 42;
const str = "hello";

const reactiveObj = toReactive(obj); // Returns reactive proxy
const reactiveNum = toReactive(num); // Returns 42 (primitive)
const reactiveStr = toReactive(str); // Returns "hello" (primitive)

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

toReadonly()

Utility function that returns a readonly proxy if the value is an object, otherwise returns the value itself.

/**
 * 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
 */
const toReadonly: <T extends unknown>(value: T) => DeepReadonly<T>;

Usage Examples:

import { toReadonly } from "@vue/reactivity";

const obj = { count: 0 };
const num = 42;

const readonlyObj = toReadonly(obj); // Returns readonly proxy
const readonlyNum = toReadonly(num); // Returns 42 (primitive)

console.log(isReadonly(readonlyObj)); // true
console.log(isReadonly(readonlyNum)); // false (primitive)

Reactive Collections

Vue's reactivity system provides special handling for JavaScript collections:

Arrays

Arrays are fully reactive with instrumented methods:

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

const arr = reactive([1, 2, 3]);

effect(() => {
  console.log("Array length:", arr.length);
  console.log("First item:", arr[0]);
});

// All these operations trigger effects
arr.push(4);
arr.pop();
arr[0] = 10;
arr.splice(1, 1, 20);
arr.sort();
arr.reverse();

Maps

Maps are reactive for all operations:

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

const map = reactive(new Map());

effect(() => {
  console.log("Map size:", map.size);
  console.log("Has 'key':", map.has("key"));
});

map.set("key", "value"); // Triggers effects
map.delete("key"); // Triggers effects
map.clear(); // Triggers effects

Sets

Sets are reactive for all operations:

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

const set = reactive(new Set());

effect(() => {
  console.log("Set size:", set.size);
  console.log("Has 'item':", set.has("item"));
});

set.add("item"); // Triggers effects
set.delete("item"); // Triggers effects
set.clear(); // Triggers effects

Types

// Reactive object types
type Reactive<T> = UnwrapNestedRefs<T> & (T extends readonly any[] ? ReactiveMarker : {});
type DeepReadonly<T> = { readonly [P in keyof T]: T[P] extends object ? DeepReadonly<T[P]> : T[P] };
type ShallowReactive<T> = T & ReactiveMarker;

// Utility types
type UnwrapNestedRefs<T> = T extends Ref ? T : UnwrapRefSimple<T>;
type Raw<T> = T & { [RawSymbol]?: true };

// Target interface for reactive objects
interface Target {
  [ReactiveFlags.SKIP]?: boolean;
  [ReactiveFlags.IS_REACTIVE]?: boolean;
  [ReactiveFlags.IS_READONLY]?: boolean;
  [ReactiveFlags.IS_SHALLOW]?: boolean;
  [ReactiveFlags.RAW]?: any;
}

// Marker interfaces
interface ReactiveMarker {
  [ReactiveMarkerSymbol]?: void;
}

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