Loading source
Pulling the file list, source metadata, and syntax-aware rendering for this listing.
Source from repo
Vue 3.5+ Composition API reference with progressive sub-file loading for components, composables, reactivity, and testing.
Files
Skill
Size
Entrypoint
Format
Open file
Syntax-highlighted preview of this file as included in the skill package.
references/reactivity.md
1---2name: Vue Reactivity System3description: Core reactivity primitives - ref, reactive, computed, and watchers4---56# Vue Reactivity System78Vue's reactivity system enables automatic tracking of state changes and DOM updates.910## ref()1112Create reactive primitive values with `ref()`. Access/modify via `.value` in JavaScript, auto-unwrapped in templates.1314```ts15import { ref } from 'vue'1617const count = ref(0)18console.log(count.value) // 019count.value++20```2122```vue23<script setup lang="ts">24import { ref } from 'vue'2526const count = ref(0)2728function increment() {29count.value++30}31</script>3233<template>34<button @click="increment">{{ count }}</button>35</template>36```3738### Typing refs3940```ts41import { ref } from 'vue'42import type { Ref } from 'vue'4344// Type inference45const year = ref(2020) // Ref<number>4647// Explicit generic48const name = ref<string | null>(null)4950// Ref type annotation51const id: Ref<string | number> = ref('abc')52```5354## reactive()5556Create reactive objects. No `.value` needed, but cannot reassign the entire object.5758```ts59import { reactive } from 'vue'6061interface State {62count: number63name: string64}6566const state: State = reactive({67count: 0,68name: 'Vue'69})7071state.count++ // reactive72```7374### Limitations of reactive()75761. **Only works with objects** - not primitives772. **Cannot replace entire object** - loses reactivity783. **Destructuring loses reactivity** - use `toRefs()` instead7980```ts81const state = reactive({ count: 0 })8283// ❌ Loses reactivity84let { count } = state8586// ✅ Use toRefs87import { toRefs } from 'vue'88const { count } = toRefs(state)89```9091## Recommendation9293Use `ref()` as the primary API for declaring reactive state - it works with any value type and has consistent behavior.9495## Deep Reactivity9697Both `ref()` and `reactive()` are deeply reactive by default:9899```ts100const obj = ref({101nested: { count: 0 },102arr: ['foo', 'bar']103})104105// These trigger updates106obj.value.nested.count++107obj.value.arr.push('baz')108```109110Use `shallowRef()` or `shallowReactive()` to opt out of deep reactivity for performance.111112## DOM Update Timing113114DOM updates are batched and asynchronous. Use `nextTick()` to wait for updates:115116```ts117import { ref, nextTick } from 'vue'118119const count = ref(0)120121async function increment() {122count.value++123await nextTick()124// DOM is now updated125}126```127128## Ref Unwrapping Rules129130- **In templates**: Top-level refs auto-unwrap131- **In reactive objects**: Refs auto-unwrap when accessed as properties132- **In arrays/collections**: Refs do NOT auto-unwrap133134```ts135const count = ref(0)136const state = reactive({ count })137138console.log(state.count) // 0 (unwrapped)139140const books = reactive([ref('Vue Guide')])141console.log(books[0].value) // Need .value142```143144## computed()145146Derive values from reactive state with automatic caching. Only re-evaluates when dependencies change.147148```ts149import { ref, computed } from 'vue'150151const firstName = ref('John')152const lastName = ref('Doe')153154// Readonly computed155const fullName = computed(() => `${firstName.value} ${lastName.value}`)156157// Writable computed158const fullNameWritable = computed({159get() {160return `${firstName.value} ${lastName.value}`161},162set(newValue: string) {163[firstName.value, lastName.value] = newValue.split(' ')164}165})166```167168### Computed Best Practices169170- **Getters should be pure** - no side effects, no mutating other state171- **Don't mutate computed values** - mutate the source instead172- **Use computed over methods** for derived data (caching benefit)173174```ts175// ✅ Cached - only recalculates when items changes176const activeItems = computed(() => items.value.filter(x => x.active))177178// ❌ Not cached - runs on every render179function getActiveItems() {180return items.value.filter(x => x.active)181}182```183184## watch()185186Explicitly watch reactive sources and run side effects when they change. Lazy by default.187188```ts189import { ref, watch } from 'vue'190191const id = ref(1)192193watch(id, async (newId, oldId) => {194const data = await fetchData(newId)195// handle data...196})197```198199### Watch Source Types200201```ts202const x = ref(0)203const obj = reactive({ count: 0 })204205// Single ref206watch(x, (newX) => console.log(newX))207208// Getter function209watch(() => obj.count, (count) => console.log(count))210211// Multiple sources212watch([x, () => obj.count], ([newX, newCount]) => {213console.log(newX, newCount)214})215```216217### Watch Options218219```ts220watch(source, callback, {221immediate: true, // Run immediately on creation222deep: true, // Watch nested properties223once: true, // Trigger only once (3.4+)224flush: 'post' // Run after DOM update225})226```227228## watchEffect()229230Automatically tracks dependencies and runs immediately. Re-runs when any tracked dependency changes.231232```ts233import { ref, watchEffect } from 'vue'234235const todoId = ref(1)236const data = ref(null)237238watchEffect(async () => {239const response = await fetch(`/api/todos/${todoId.value}`)240data.value = await response.json()241})242```243244### watch vs watchEffect245246| Feature | watch | watchEffect |247| ------------------- | ---------------- | --------------------- |248| Dependency tracking | Explicit | Automatic |249| Lazy | Yes | No (immediate) |250| Access old value | Yes | No |251| Best for | Specific sources | Multiple dependencies |252253## Watcher Cleanup (3.5+)254255Cancel stale async operations:256257```ts258import { watch, onWatcherCleanup } from 'vue'259260watch(id, async (newId) => {261const controller = new AbortController()262263fetch(`/api/${newId}`, { signal: controller.signal })264265onWatcherCleanup(() => controller.abort())266})267```268269## Stopping Watchers270271```ts272const stop = watch(source, callback)273const stop2 = watchEffect(() => { /* ... */ })274275// Stop manually276stop()277stop2()278279// Pause/Resume (3.5+)280const { stop, pause, resume } = watchEffect(() => { /* ... */ })281```282283<!--284Source references:285- https://vuejs.org/guide/essentials/reactivity-fundamentals.html286- https://vuejs.org/guide/essentials/computed.html287- https://vuejs.org/guide/essentials/watchers.html288- https://vuejs.org/api/reactivity-core.html289-->290