JAVASCRIPT
Using `watchEffect` vs `watch` for Reactive Side Effects in Vue 3
Understand the differences and ideal use cases for `watch` and `watchEffect` in Vue 3 for managing reactive side effects, from specific data changes to automatic dependency tracking.
// App.vue
<template>
<div class="container">
<h1>`watch` vs `watchEffect` in Vue 3</h1>
<div class="section">
<h2>`watch` Example</h2>
<p>Explicitly watch one or more reactive sources.</p>
<label>Count: <input type="number" v-model.number="count" /></label>
<p>Message: <input type="text" v-model="message" /></label>
<p>Watched Count Value: <strong>{{ watchedCountValue }}</strong></p>
<p>Watched Message Value: <strong>{{ watchedMessageValue }}</strong></p>
<p>Count Watcher Log: <em>{{ countWatcherLog }}</em></p>
<p>Message Watcher Log: <em>{{ messageWatcherLog }}</em></p>
</div>
<div class="section">
<h2>`watchEffect` Example</h2>
<p>Automatically re-runs when any of its reactive dependencies change.</p>
<label>User Name: <input type="text" v-model="userName" /></label>
<label>User Age: <input type="number" v-model.number="userAge" /></label>
<p>watchEffect Log: <em>{{ watchEffectLog }}</em></p>
</div>
<div class="section">
<h2>Comparison</h2>
<ul>
<li>`watch` is <strong>lazy</strong> (doesn't run on setup), `watchEffect` is <strong>immediate</strong>.</li>
<li>`watch` requires <strong>explicit sources</strong>, `watchEffect` <strong>auto-tracks</strong>.</li>
<li>`watch` provides <strong>old and new values</strong>, `watchEffect` does not.</li>
</ul>
</div>
</div>
</template>
<script setup>
import { ref, watch, watchEffect } from 'vue';
// --- `watch` Example ---
const count = ref(0);
const message = ref('Hello Vue!');
const watchedCountValue = ref(0);
const watchedMessageValue = ref('');
const countWatcherLog = ref('Not run yet.');
const messageWatcherLog = ref('Not run yet.');
// Watch a single reactive source (count)
watch(count, (newCount, oldCount) => {
watchedCountValue.value = newCount;
countWatcherLog.value = `Count changed from ${oldCount} to ${newCount}.`;
console.log('Watch (count):', { newCount, oldCount });
});
// Watch a single reactive source (message) with options
watch(message, (newMessage, oldMessage) => {
watchedMessageValue.value = newMessage;
messageWatcherLog.value = `Message changed from '${oldMessage}' to '${newMessage}'.`;
console.log('Watch (message):', { newMessage, oldMessage });
}, { immediate: true }); // `immediate: true` runs the watcher immediately on setup
// Watch multiple reactive sources
watch([count, message], ([newCount, newMessage], [oldCount, oldMessage]) => {
console.log('Watch (multiple sources):', { newCount, newMessage, oldCount, oldMessage });
// This watcher will run if either count or message changes
});
// --- `watchEffect` Example ---
const userName = ref('John Doe');
const userAge = ref(30);
const watchEffectLog = ref('Not run yet.');
// watchEffect automatically tracks dependencies within its callback
watchEffect(() => {
// This effect will re-run whenever userName or userAge changes
watchEffectLog.value = `User details: ${userName.value} is ${userAge.value} years old. (Last updated: ${new Date().toLocaleTimeString()})`;
console.log('watchEffect: User Name or Age changed.', userName.value, userAge.value);
});
// Cleanup function for watchEffect (optional)
// watchEffect((onCleanup) => {
// const timer = setTimeout(() => {
// console.log('Effect with cleanup ran');
// }, 1000);
// onCleanup(() => {
// clearTimeout(timer);
// console.log('Effect cleanup ran');
// });
// });
</script>
<style>
.container {
max-width: 900px;
margin: 50px auto;
padding: 25px;
border: 1px solid #eee;
border-radius: 8px;
box-shadow: 0 4px 12px rgba(0,0,0,0.05);
font-family: Arial, sans-serif;
}
.section {
margin-bottom: 30px;
padding: 15px;
border: 1px dashed #ccc;
border-radius: 6px;
background-color: #f9f9f9;
}
h1, h2 {
color: #333;
text-align: center;
margin-bottom: 20px;
}
p {
margin: 8px 0;
}
label {
display: block;
margin-bottom: 10px;
font-weight: bold;
}
input[type="number"], input[type="text"] {
padding: 8px;
border: 1px solid #ddd;
border-radius: 4px;
margin-left: 10px;
min-width: 150px;
}
p strong {
color: #007bff;
}
p em {
font-size: 0.9em;
color: #666;
}
ul {
list-style-type: disc;
padding-left: 20px;
}
li {
margin-bottom: 5px;
}
</style>
How it works: This snippet illustrates the key differences and practical applications of Vue 3's `watch` and `watchEffect` functions for managing reactive side effects. `watch` allows you to explicitly observe one or more reactive data sources (refs, reactive objects, getters) and execute a callback function when they change, providing access to both new and old values. In contrast, `watchEffect` automatically tracks any reactive dependencies accessed within its callback and re-runs whenever any of those dependencies change, making it convenient for effects where the dependencies are dynamic or numerous. Both are powerful tools for reacting to state changes, but serve different scenarios, with `watchEffect` being 'eager' (runs immediately on setup) and `watch` being 'lazy' by default.