Vue’s reactivity system tracks data changes and automatically updates the DOM. In the Composition API, you create reactive state with ref, reactive, computed, and watch.

ref — Reactive Primitives

Use ref() for any value. Access and mutate it with .value in script, but not in templates:

  <script setup>
import { ref } from 'vue';

const count = ref(0);
const name = ref('Alice');
const isActive = ref(true);

function increment() {
  count.value++;
}
</script>

<template>
  <p>{{ name }} clicked {{ count }} times</p>
  <button @click="increment">+1</button>
</template>
  

reactive — Reactive Objects

Use reactive() for objects and arrays. No .value needed:

  <script setup>
import { reactive } from 'vue';

const user = reactive({
  name: 'Bob',
  age: 30,
  address: { city: 'Portland' },
});

function birthday() {
  user.age++;
  user.address.city = 'Seattle';
}
</script>

<template>
  <p>{{ user.name }}, age {{ user.age }} — {{ user.address.city }}</p>
  <button @click="birthday">Birthday</button>
</template>
  

Tip: Prefer ref for primitives and when reassigning entire objects. Use reactive for stable object shapes.

computed — Derived State

computed creates cached, derived values that update when dependencies change:

  <script setup>
import { ref, computed } from 'vue';

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

const fullName = computed(() => `${firstName.value} ${lastName.value}`);

const items = ref([10, 20, 30]);
const total = computed(() => items.value.reduce((sum, n) => sum + n, 0));
</script>

<template>
  <p>{{ fullName }}</p>
  <p>Total: {{ total }}</p>
</template>
  

watch — Side Effects

React to data changes with watch:

  <script setup>
import { ref, watch } from 'vue';

const searchQuery = ref('');
const results = ref([]);

watch(searchQuery, async (newQuery) => {
  if (newQuery.length < 2) {
    results.value = [];
    return;
  }
  const response = await fetch(`/api/search?q=${newQuery}`);
  results.value = await response.json();
});
</script>

<template>
  <input v-model="searchQuery" placeholder="Search..." />
  <ul>
    <li v-for="item in results" :key="item.id">{{ item.name }}</li>
  </ul>
</template>
  

watchEffect — Automatic Tracking

watchEffect runs immediately and re-runs when any tracked dependency changes:

  import { ref, watchEffect } from 'vue';

const count = ref(0);

watchEffect(() => {
  document.title = `Count: ${count.value}`;
});
  

ref vs reactive Summary

API Best for Access in script
ref Primitives, reassignable values .value
reactive Objects, nested data Direct property access
computed Derived values .value (script)
watch Side effects on change

Next: organize UI into reusable components with props, emits, and slots.