# How to Use the watch Function in Vue 3 Setup

> Learn how to use the watch function in Vue 3 setup. Create reactive effects to track changes and execute callbacks efficiently. Master Vue 3 watchers today.

- Repository: [Vue/core](https://github.com/vuejs/core)
- Tags: how-to-guide
- Published: 2026-02-13

---

**In Vue 3, the `watch` function imported from `vue` creates a reactive effect inside `setup()` that executes a callback whenever the tracked source changes, receiving the new value, old value, and an optional cleanup function.**

The `watch` function is a fundamental reactivity API in the Vue 3 Composition API, providing a declarative way to perform side effects in response to state changes. According to the vuejs/core source code, the implementation lives in [`packages/runtime-core/src/watch.ts`](https://github.com/vuejs/core/blob/main/packages/runtime-core/src/watch.ts) and builds on top of Vue's proxy-based reactivity system to handle dependency tracking and scheduling.

## Core Implementation and Source Normalization in vuejs/core

The `watch` function is implemented in [`packages/runtime-core/src/watch.ts`](https://github.com/vuejs/core/blob/main/packages/runtime-core/src/watch.ts) and re-exported via [`packages/runtime-core/src/apiWatch.ts`](https://github.com/vuejs/core/blob/main/packages/runtime-core/src/apiWatch.ts) as part of the public Composition API surface. When you invoke `watch` inside a component's `setup()`, the runtime performs **source normalization** to convert your input—whether a getter function, a `ref`, a `reactive` object, or an array of these—into a unified `sourceGetter`.

This normalization allows the core scheduler in [`packages/runtime-core/src/component.ts`](https://github.com/vuejs/core/blob/main/packages/runtime-core/src/component.ts) to treat every reactive dependency uniformly. The watcher collects accessed properties into a `Dep` set, and when any dependency changes, the scheduler queues your callback to run on the next micro-task (or immediately if `flush: 'sync'` is specified).

## Basic Usage with Refs

The simplest use case watches a single `ref` and receives both the new and previous values in the callback.

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

export default {
  setup() {
    const count = ref(0)

    watch(count, (newVal, oldVal) => {
      console.log(`count changed from ${oldVal} → ${newVal}`)
    })

    return { count }
  }
}

```

In this pattern, `watch` automatically unwraps the ref and begins tracking its value changes through Vue's reactivity proxy.

## Deep Observation of Reactive Objects

When watching a `reactive` object or nested properties within one, you must use a getter function and enable the `deep` option to force recursive traversal. According to the implementation in [`packages/runtime-core/src/watch.ts`](https://github.com/vuejs/core/blob/main/packages/runtime-core/src/watch.ts), the `deep` flag triggers a `traverse` of the source to register nested properties as dependencies.

```typescript
import { reactive, watch } from 'vue'

export default {
  setup() {
    const state = reactive({ user: { name: 'Alice' } })

    watch(
      () => state.user,
      (newUser, oldUser) => {
        console.log('User object changed', newUser)
      },
      { deep: true }
    )

    return { state }
  }
}

```

Without the getter function `() => state.user`, the watcher cannot properly establish reactive tracking on the specific nested object.

## Tracking Multiple Sources Simultaneously

You can observe several reactive sources at once by passing an array to `watch`. The callback receives arrays of new and old values corresponding to the order of the sources.

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

export default {
  setup() {
    const firstName = ref('John')
    const lastName = ref('Doe')

    watch([firstName, lastName], ([newFirst, newLast], [oldFirst, oldLast]) => {
      console.log(`Full name changed to ${newFirst} ${newLast}`)
    })

    return { firstName, lastName }
  }
}

```

This approach leverages the same normalization logic in [`packages/runtime-core/src/watch.ts`](https://github.com/vuejs/core/blob/main/packages/runtime-core/src/watch.ts) to create a composite getter that tracks all array members.

## Immediate Execution and Cleanup Patterns

### Immediate Invocation on Setup

Use the `immediate` option to execute the callback immediately after the watcher is created, before any state mutations occur. This is useful for initializing data based on the current reactive state.

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

export default {
  setup() {
    const query = ref('')

    watch(
      query,
      (q) => {
        console.log('Search for:', q)
      },
      { immediate: true }
    )

    return { query }
  }
}

```

### Cleanup Functions for Async Operations

The `watch` callback receives a third argument, `onCleanup`, which allows you to register cleanup logic that runs before the callback re-executes. This is essential for aborting previous asynchronous work, such as pending API calls when a search term changes rapidly.

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

export default {
  setup() {
    const term = ref('')

    watch(
      term,
      (newTerm, _, onCleanup) => {
        const controller = new AbortController()
        fetch(`/search?q=${newTerm}`, { signal: controller.signal })
          .then(r => r.json())
          .then(data => console.log(data))

        onCleanup(() => controller.abort())
      },
      { immediate: true }
    )

    return { term }
  }
}

```

The scheduler in [`packages/runtime-core/src/watch.ts`](https://github.com/vuejs/core/blob/main/packages/runtime-core/src/watch.ts) ensures that `onCleanup` is invoked before running the next iteration of your callback, preventing race conditions in side effects.

## Summary

- The `watch` function in Vue 3's `setup()` is implemented in [`packages/runtime-core/src/watch.ts`](https://github.com/vuejs/core/blob/main/packages/runtime-core/src/watch.ts) and integrates with the core reactivity system defined in [`packages/runtime-core/src/reactivity.ts`](https://github.com/vuejs/core/blob/main/packages/runtime-core/src/reactivity.ts).
- **Source normalization** converts refs, reactive objects, or arrays into unified getters for uniform dependency tracking.
- The callback receives **newValue**, **oldValue**, and an **onCleanup** handler for managing async side effects between executions.
- Use `{ deep: true }` when watching nested properties inside reactive objects, and `{ immediate: true }` to trigger the callback during initialization.
- Multiple sources can be watched using array syntax, with corresponding value arrays returned to the callback.

## Frequently Asked Questions

### What is the difference between `watch` and `watchEffect` in Vue 3?

`watch` requires you to explicitly specify the reactive source to track, whereas `watchEffect` automatically tracks all reactive dependencies accessed during its execution. According to the vuejs/core implementation, `watch` is a thin wrapper around lower-level utilities that provides access to the previous value and supports the `deep` option, while `watchEffect` is more suitable for cases where you do not need to know which specific dependency triggered the update or access the previous state.

### Why do I need to use a getter function when watching reactive objects?

Vue's reactivity system requires an explicit getter to establish a dependency on a specific property of a reactive object. When you pass a getter like `() => state.user` to `watch`, the function executes during dependency collection in [`packages/runtime-core/src/reactivity.ts`](https://github.com/vuejs/core/blob/main/packages/runtime-core/src/reactivity.ts), allowing Vue's proxies to track that specific path. Passing the raw reactive object would not provide the granular tracking needed to detect changes to nested properties unless you also enable `deep: true`.

### When should I use the `flush` option?

By default, Vue queues `watch` callbacks to run after the current synchronous execution cycle completes (micro-task timing). Set `flush: 'sync'` to force the callback to run immediately when the dependency changes, which is useful when you need to observe changes before the DOM updates or to maintain strict timing consistency with external libraries.

### How do I clean up side effects when a watcher re-runs or the component unmounts?

Use the `onCleanup` function passed as the third argument to your `watch` callback to register cleanup logic for async operations like pending API calls. Additionally, as implemented in [`packages/runtime-core/src/component.ts`](https://github.com/vuejs/core/blob/main/packages/runtime-core/src/component.ts), Vue automatically manages watcher teardown when the component instance is destroyed, ensuring that reactive effects registered during `setup()` do not leak after the component unmounts.