Fading Coder

One Final Commit for the Last Sprint

Home > Tech > Content

Key Differences Between watch and watchEffect in Vue 3

Tech 1

Both watch and watchEffect are built on Vue 3's shared reactivity system, differing only in their dependency tracking behavior and usage syntax.

Reactivity Dependency Tracking

To understand the difference between the two APIs, it helps to first cover how Vue 3's reactivity system operates at a high level. Below is a simplified implementation of the ref utility to demonstrate core mechanics:

const ref = (initVal) => {
  let internalVal = initVal
  return {
    get value() {
      track(this, 'value') // Register access as a dependency
      return internalVal
    },
    set value(newVal) {
      internalVal = newVal
      trigger(this, 'value') // Run all registered dependents
    }
  }
}

When a reactive property is accessed, the track method records the current running effect as a dependency of that property. When the property is updated, trigger executes all effects that depend on the property. All reactive utilities including computed, watch, and watchEffect are implemented on top of this shared effect system.

const orderCount = ref(3)
console.log(orderCount.value) // Calls track() for orderCount.value
orderCount.value = 5 // Calls trigger() for orderCount.value

To collect all dependencies for a given function, you can wrap its execution between tracking start and stop operations:

const orderCount = ref(3)

function printOrderTotal() {
  console.log(`Current active orders: ${orderCount.value}`)
}

function gatherDependencies() {
  activateTracking()
  printOrderTotal() // All reactive values accessed here are recorded
  deactivateTracking()
}

This process lets the reactivity system know that printOrderTotal should be rerun whenever orderCount updates.

watch API

The base watch API accepts two distinct arguments: a dependency getter function, and a callback that executes only when the value returned by the getter changes. Shorthand versions that accept a ref or reactive object directly are just syntax sugar over this base format.

watch(
  // Dependency getter, runs once on initialization to collect dependencies
  () => userProfile.value.age,
  // Callback, runs exclusively when the getter's return value updates
  (newAge, oldAge) => {
    sendAgeVerificationRequest(newAge)
  }
)

The dependency getter runs exactly once by default, and no dependency tracking is performed on the callback itself. This means the callback can reference values not declared in the getter without those values triggering runs of the callback.

watchEffect API

watchEffect merges the dependancy getter and callback into a single function. It automatically tracks all reactive values accessed inside its callback, and reruns the entire function whenever any of those dependencies update. Unlike watch, watchEffect executes immediately on initialization, equivalent to passing the { immediate: true } option to watch.

The two snippets below have nearly identical behavior:

const clickCount = ref(0)

watchEffect(() => {
  console.log(`Total button clicks: ${clickCount.value}`)
})

// Equivalent watch implementation
watch(
  () => clickCount.value,
  () => {
    console.log(`Total button clicks: ${clickCount.value}`)
  },
  { immediate: true }
)

A unique trait of watchEffect is that it re-collects dependencies on every run, so dynamically added dependencies are automatically registered:

const clickCount = ref(0)
const isLoggingEnabled = ref(false)

watchEffect(() => {
  if (isLoggingEnabled.value) {
    console.log(`Current click total: ${clickCount.value}`)
  }
})

// Initial run: isLoggingEnabled is false, only isLoggingEnabled is marked as a dependency
clickCount.value += 1 // No output, clickCount is not yet tracked
isLoggingEnabled.value = true // Triggers effect, outputs "Current click total: 1"
clickCount.value += 1 // clickCount is now tracked, outputs "Current click total: 2"
isLoggingEnabled.value = false // Triggers effect, no output
clickCount.value += 1 // Still triggers effect, even though clickCount is no longer used in the conditional

computed can be viewed as a synchronous variant of watchEffect that includes caching for its return value.

Prefer watch for most use cases, as explicit dependency declaration prevents unintended reruns and reduces the risk of accidentally introducing new dependenceis during code refactoring. Use watchEffect for simple logic where the callback is tightly coupled to all the reactive values it uses, or when you want to avoid manually listing all required dependencies.

Related Articles

Understanding Strong and Weak References in Java

Strong References Strong reference are the most prevalent type of object referencing in Java. When an object has a strong reference pointing to it, the garbage collector will not reclaim its memory. F...

Comprehensive Guide to SSTI Explained with Payload Bypass Techniques

Introduction Server-Side Template Injection (SSTI) is a vulnerability in web applications where user input is improper handled within the template engine and executed on the server. This exploit can r...

Implement Image Upload Functionality for Django Integrated TinyMCE Editor

Django’s Admin panel is highly user-friendly, and pairing it with TinyMCE, an effective rich text editor, simplifies content management significantly. Combining the two is particular useful for bloggi...

Leave a Comment

Anonymous

◎Feel free to join the discussion and share your thoughts.