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

computed.mddocs/

Computed Values

Computed values are cached derived values that automatically update when their dependencies change. They provide an efficient way to create values based on reactive state while avoiding unnecessary computations.

Capabilities

computed()

Creates a computed ref that automatically tracks its dependencies and caches the result. The computed value only re-evaluates when its dependencies change.

/**
 * Creates a readonly computed ref from a getter function
 * @param getter - Function that computes the value
 * @param debugOptions - Optional debug configuration
 * @returns A readonly computed ref
 */
function computed<T>(
  getter: ComputedGetter<T>, 
  debugOptions?: DebuggerOptions
): ComputedRef<T>;

/**
 * Creates a writable computed ref with getter and setter
 * @param options - Object with get and set functions
 * @param debugOptions - Optional debug configuration
 * @returns A writable computed ref
 */
function computed<T, S = T>(
  options: WritableComputedOptions<T, S>, 
  debugOptions?: DebuggerOptions
): WritableComputedRef<T, S>;

type ComputedGetter<T> = (oldValue?: T) => T;
type ComputedSetter<T> = (newValue: T) => void;

Usage Examples:

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

// Basic computed value
const count = ref(1);
const doubleCount = computed(() => count.value * 2);

console.log(doubleCount.value); // 2
count.value = 5;
console.log(doubleCount.value); // 10

// Computed with multiple dependencies
const firstName = ref("John");
const lastName = ref("Doe");
const fullName = computed(() => `${firstName.value} ${lastName.value}`);

console.log(fullName.value); // "John Doe"
firstName.value = "Jane";
console.log(fullName.value); // "Jane Doe"

// Complex computed logic
const todos = ref([
  { id: 1, text: "Learn Vue", completed: false },
  { id: 2, text: "Build app", completed: true }
]);

const completedTodos = computed(() => 
  todos.value.filter(todo => todo.completed)
);

const incompleteTodos = computed(() => 
  todos.value.filter(todo => !todo.completed)
);

const todoStats = computed(() => ({
  total: todos.value.length,
  completed: completedTodos.value.length,
  remaining: incompleteTodos.value.length,
  progress: completedTodos.value.length / todos.value.length
}));

console.log(todoStats.value.progress); // 0.5

Writable Computed

Create computed values that can be both read from and written to by providing getter and setter functions.

interface WritableComputedOptions<T, S = T> {
  get: ComputedGetter<T>;
  set: ComputedSetter<S>;
}

interface WritableComputedRef<T, S = T> extends ComputedRef<T> {
  set value(value: S);
}

Usage Examples:

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

const firstName = ref("John");
const lastName = ref("Doe");

// Writable computed that combines and splits full name
const fullName = computed({
  get: () => `${firstName.value} ${lastName.value}`,
  set: (value: string) => {
    const [first, last] = value.split(" ");
    firstName.value = first || "";
    lastName.value = last || "";
  }
});

console.log(fullName.value); // "John Doe"

// Writing to computed updates the source refs
fullName.value = "Jane Smith";
console.log(firstName.value); // "Jane"
console.log(lastName.value); // "Smith"

// Writable computed for form data
const user = ref({
  firstName: "Alice",
  lastName: "Johnson",
  email: "alice@example.com"
});

const displayName = computed({
  get: () => `${user.value.firstName} ${user.value.lastName}`,
  set: (name: string) => {
    const [first, ...rest] = name.split(" ");
    user.value.firstName = first || "";
    user.value.lastName = rest.join(" ") || "";
  }
});

displayName.value = "Bob Wilson"; // Updates user.firstName and user.lastName

Computed with Side Effects

While computed values should generally be pure, you can use them with careful side effects for logging or debugging.

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

const count = ref(0);

// Computed with debug logging
const expensiveComputation = computed(() => {
  console.log("Computing expensive value..."); // Side effect for debugging
  
  // Simulate expensive computation
  let result = 0;
  for (let i = 0; i < count.value * 1000; i++) {
    result += Math.random();
  }
  
  return result;
});

// The computation only runs when count changes
console.log(expensiveComputation.value); // Runs computation
console.log(expensiveComputation.value); // Returns cached value
count.value = 2; // Invalidates cache
console.log(expensiveComputation.value); // Runs computation again

Computed Error Handling

Handle errors in computed values gracefully:

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

const jsonString = ref('{"name": "Alice"}');

const parsedData = computed(() => {
  try {
    return JSON.parse(jsonString.value);
  } catch (error) {
    console.warn("Invalid JSON:", error);
    return null;
  }
});

console.log(parsedData.value); // { name: "Alice" }

jsonString.value = "invalid json";
console.log(parsedData.value); // null (with warning)

Debugging Computed Values

Use debug options to track computed value behavior:

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

const count = ref(0);

const doubleCount = computed(
  () => count.value * 2,
  {
    onTrack: (event) => {
      console.log("Computed tracked:", event);
    },
    onTrigger: (event) => {
      console.log("Computed triggered:", event);
    }
  }
);

// Access to track dependencies
console.log(doubleCount.value); // Logs tracking

// Change to trigger re-computation
count.value = 5; // Logs trigger
console.log(doubleCount.value); // New computed value

Types

// Core computed interfaces
interface ComputedRef<T = any> extends BaseComputedRef<T> {
  readonly value: T;
}

interface WritableComputedRef<T, S = T> extends BaseComputedRef<T, S> {
  set value(value: S);
  [WritableComputedRefSymbol]: true;
}

interface BaseComputedRef<T, S = T> extends Ref<T, S> {
  [ComputedRefSymbol]: true;
  effect: ReactiveEffect<T>;
}

// Function types
type ComputedGetter<T> = (oldValue?: T) => T;
type ComputedSetter<T> = (newValue: T) => void;

// Options interface
interface WritableComputedOptions<T, S = T> {
  get: ComputedGetter<T>;
  set: ComputedSetter<S>;
}

// Debug options
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>;
}

// Internal implementation (exported but marked as internal)
class ComputedRefImpl<T, S = T> implements WritableComputedRef<T, S> {
  constructor(
    getter: ComputedGetter<T>,
    setter?: ComputedSetter<S>,
    isReadonly?: boolean,
    debugOptions?: DebuggerOptions
  );
  
  get value(): T;
  set value(newValue: S);
  
  readonly effect: ReactiveEffect<T>;
  readonly [ComputedRefSymbol]: true;
  readonly [RefSymbol]: true;
}

Performance Considerations

Lazy Evaluation

Computed values are lazy - they only compute when accessed:

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

const count = ref(0);

// This computed is created but not evaluated yet
const expensiveValue = computed(() => {
  console.log("Computing..."); // Won't run until accessed
  return count.value * 1000;
});

// Now the computation runs
console.log(expensiveValue.value); // Logs "Computing..." then 0

Caching Behavior

Computed values cache their results and only re-compute when dependencies change:

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

const count = ref(1);
let computationCount = 0;

const doubled = computed(() => {
  computationCount++;
  console.log(`Computation #${computationCount}`);
  return count.value * 2;
});

// First access computes the value
console.log(doubled.value); // Logs "Computation #1", returns 2

// Second access returns cached value
console.log(doubled.value); // Returns 2 (no computation log)

// Changing dependency invalidates cache
count.value = 3;

// Next access re-computes
console.log(doubled.value); // Logs "Computation #2", returns 6

Avoiding Unnecessary Computations

Structure computed values to minimize unnecessary work:

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

const users = ref([
  { id: 1, name: "Alice", active: true },
  { id: 2, name: "Bob", active: false },
  { id: 3, name: "Charlie", active: true }
]);

// Good: Computed values that can be independently cached
const activeUsers = computed(() => 
  users.value.filter(user => user.active)
);

const userCount = computed(() => users.value.length);
const activeUserCount = computed(() => activeUsers.value.length);

// Less optimal: Single computed doing multiple calculations
const userStats = computed(() => ({
  total: users.value.length,
  active: users.value.filter(user => user.active).length, // Filters twice
  inactive: users.value.filter(user => !user.active).length
}));

// Better: Use other computed values
const optimizedUserStats = computed(() => ({
  total: userCount.value,
  active: activeUserCount.value,
  inactive: userCount.value - activeUserCount.value
}));

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