Claude
Skills
Sign in
Back

vue-reactivity-system

Included with Lifetime
$97 forever

Use when Vue reactivity system with refs, reactive, computed, and watchers. Use when managing complex state in Vue applications.

Web Dev

What this skill does


# Vue Reactivity System

Master Vue's reactivity system to build reactive, performant applications
with optimal state management and computed properties.

## Reactivity Fundamentals (Proxy-based)

Vue 3 uses JavaScript Proxies for reactivity:

```typescript
import { ref, reactive, isRef, isReactive, isProxy } from 'vue';

// ref creates reactive wrapper
const count = ref(0);
console.log(isRef(count)); // true
console.log(isProxy(count)); // false (ref itself isn't proxy)
console.log(isProxy(count.value)); // false for primitives

// reactive creates proxy
const state = reactive({ count: 0 });
console.log(isReactive(state)); // true
console.log(isProxy(state)); // true

// Proxies track access and mutations
state.count++; // Triggers reactivity
count.value++; // Triggers reactivity
```

## Ref - Reactive Primitives and Objects

### Basic Ref Usage

```typescript
import { ref } from 'vue';

// Primitives
const count = ref(0);
const name = ref('John');
const isActive = ref(true);

// Access via .value
console.log(count.value); // 0
count.value++; // Update triggers reactivity

// Objects (wrapped in proxy)
const user = ref({
  name: 'John',
  age: 30
});

// Nested properties are reactive
user.value.age++; // Triggers reactivity

// Can replace entire object
user.value = { name: 'Jane', age: 25 }; // Works!
```

### Shallow Ref

```typescript
import { shallowRef, triggerRef } from 'vue';

// Only .value is reactive, not nested properties
const state = shallowRef({
  count: 0,
  nested: { value: 0 }
});

// This triggers reactivity
state.value = { count: 1, nested: { value: 1 } };

// This does NOT trigger reactivity
state.value.count++; // No update!

// Manually trigger
state.value.count++;
triggerRef(state); // Force update
```

### Custom Ref

```typescript
import { customRef } from 'vue';

// Debounced ref
function useDebouncedRef<T>(value: T, delay = 200) {
  let timeout: ReturnType<typeof setTimeout>;

  return customRef((track, trigger) => ({
    get() {
      track(); // Tell Vue to track this
      return value;
    },
    set(newValue: T) {
      clearTimeout(timeout);
      timeout = setTimeout(() => {
        value = newValue;
        trigger(); // Tell Vue to re-render
      }, delay);
    }
  }));
}

// Usage
const searchQuery = useDebouncedRef('', 300);

// Updates are debounced
searchQuery.value = 'a'; // Doesn't trigger immediately
searchQuery.value = 'ab'; // Still waiting
searchQuery.value = 'abc'; // Triggers after 300ms
```

## Reactive - Deep Reactive Objects

### Basic Reactive Usage

```typescript
import { reactive } from 'vue';

// Create deep reactive object
const state = reactive({
  user: {
    name: 'John',
    profile: {
      email: '[email protected]',
      settings: {
        theme: 'dark'
      }
    }
  },
  posts: []
});

// All nested properties are reactive
state.user.profile.settings.theme = 'light'; // Triggers reactivity
state.posts.push({ id: 1, title: 'Post' }); // Triggers reactivity
```

### Shallow Reactive

```typescript
import { shallowReactive } from 'vue';

// Only root-level properties are reactive
const state = shallowReactive({
  count: 0,
  nested: { value: 0 }
});

// This triggers reactivity
state.count++; // Works

// This does NOT trigger reactivity
state.nested.value++; // No update!

// But replacing works
state.nested = { value: 1 }; // Triggers reactivity
```

### Reactive Arrays

```typescript
import { reactive } from 'vue';

const list = reactive<number[]>([]);

// Mutating methods trigger reactivity
list.push(1); // Reactive
list.pop(); // Reactive
list.splice(0, 1); // Reactive
list.sort(); // Reactive
list.reverse(); // Reactive

// Replacement triggers reactivity
const newList = reactive([1, 2, 3]);
```

### Reactive Collections

```typescript
import { reactive } from 'vue';

// Map
const map = reactive(new Map<string, number>());
map.set('count', 0); // Reactive
map.delete('count'); // Reactive

// Set
const set = reactive(new Set<number>());
set.add(1); // Reactive
set.delete(1); // Reactive

// WeakMap and WeakSet
const weakMap = reactive(new WeakMap());
const weakSet = reactive(new WeakSet());
```

## Readonly - Prevent Mutations

```typescript
import { reactive, readonly, isReadonly } from 'vue';

const state = reactive({ count: 0 });
const readonlyState = readonly(state);

console.log(isReadonly(readonlyState)); // true

// Cannot mutate
readonlyState.count++; // Warning in dev mode

// Original is still mutable
state.count++; // Works, updates readonly view too

// Deep readonly
const deepState = reactive({
  nested: { value: 0 }
});

const deepReadonly = readonly(deepState);
deepReadonly.nested.value++; // Warning! Deep readonly
```

## ToRef and ToRefs - Preserve Reactivity

### ToRefs - Convert Reactive to Refs

```typescript
import { reactive, toRefs } from 'vue';

const state = reactive({
  count: 0,
  name: 'John'
});

// Destructuring loses reactivity
const { count, name } = state; // NOT reactive!

// Use toRefs to preserve reactivity
const { count: countRef, name: nameRef } = toRefs(state);

// Now reactive
countRef.value++; // Updates state.count
console.log(state.count); // 1
```

### ToRef - Create Ref from Property

```typescript
import { reactive, toRef } from 'vue';

const state = reactive({
  count: 0
});

// Create ref to specific property
const countRef = toRef(state, 'count');

countRef.value++; // Updates state.count
console.log(state.count); // 1

// Non-existent properties
const missingRef = toRef(state, 'missing');
missingRef.value = 'now exists'; // Adds to state!
```

## Unref and IsRef - Ref Utilities

```typescript
import { ref, unref, isRef } from 'vue';

const count = ref(0);
const plain = 0;

// unref: unwrap if ref, return value otherwise
console.log(unref(count)); // 0
console.log(unref(plain)); // 0

// Useful for handling ref or value
function double(value: number | Ref<number>): number {
  return unref(value) * 2;
}

double(count); // 0
double(5); // 10

// isRef: check if value is ref
if (isRef(count)) {
  console.log(count.value);
} else {
  console.log(count);
}
```

## Computed - Derived State

### Basic Computed

```typescript
import { ref, computed } from 'vue';

const count = ref(0);

const doubled = computed(() => count.value * 2);

console.log(doubled.value); // 0
count.value = 5;
console.log(doubled.value); // 10

// Computed is cached
const expensive = computed(() => {
  console.log('Computing...');
  return count.value * 2;
});

console.log(expensive.value); // Computing... 0
console.log(expensive.value); // 0 (cached, no log)
count.value = 1;
console.log(expensive.value); // Computing... 2
```

### Writable Computed

```typescript
import { ref, computed } from 'vue';

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

const fullName = computed({
  get() {
    return `${firstName.value} ${lastName.value}`;
  },
  set(value) {
    [firstName.value, lastName.value] = value.split(' ');
  }
});

console.log(fullName.value); // John Doe
fullName.value = 'Jane Smith';
console.log(firstName.value); // Jane
console.log(lastName.value); // Smith
```

### Computed Debugging

```typescript
import { ref, computed } from 'vue';

const count = ref(0);

const doubled = computed(
  () => count.value * 2,
  {
    onTrack(e) {
      console.log('Tracked:', e);
    },
    onTrigger(e) {
      console.log('Triggered:', e);
    }
  }
);
```

## Watch - React to Changes

### Watch Single Source

```typescript
import { ref, watch } from 'vue';

const count = ref(0);

// Basic watch
watch(count, (newValue, oldValue) => {
  console.log(`Count: ${oldValue} -> ${newValue}`);
});

// With options
watch(
  count,
  (newValue, oldValue) => {
    console.log('Count changed');
  },
  {
    immediate: true, // Run immediately
    flush: 'post', // Timing: 'pre' | 'post' | 'sync'
    onTrack(e) { console.log('Tracked:', e); },
    onTrigger(e) { console.log('Triggered:', e); }
  }
);
```

### Watch Multiple Sources

```typescript
import { ref, watch } fr

Related in Web Dev