Ruttyj
Ruttyj

Reputation: 933

Vue 3 Composition API - watchEffect vs. watch

So I have been learning the Vue Composition API and was wondering what the difference between watchEffect and watch is. Watch says it's the same as the Vue 2 watch, so I'm guessing watchEffect is like the 2.0 of that? I'm wondering if there is any specific cases where one would have great advantages over the other like in the case of stopping the watchEffect and then reactivating it instead of using a boolean in a regular watch... or are they just basically different ways of writing the same thing.

Thanks!

Reference:

watcheffect: https://vue-composition-api-rfc.netlify.com/api.html#watcheffect

watch: https://vue-composition-api-rfc.netlify.com/api.html#watch

Upvotes: 36

Views: 30840

Answers (5)

Marinos An
Marinos An

Reputation: 10828

TLDR:

  • watchEffect(callback) will register the callback() as fireOnce-listener to all the properties accessed at the last execution of callback(). That is also the reason why the first execution of callback() is mandatory.
  • watch(refsArr, callback) will register the callback() as a fireAlways-listener (unless once=true option is provided) to the reactive property/ies passed ( refsArr). The previous and current values of the reactive properties are provided to the callback()
  • watch(getter, callback) will register the getter() as a fireOnce-listener to all the properties accessed at the last execution of getter(). The result of getter() determines whether callback will be executed (e.g. if it returns a value that is different from its previous execution). The return value of getter() does not affect any of the reactive properties nor is it mandatory that the returned value is anyhow related to them (though it is advised that it at least derives from them). If getter() returns always the same value, the corresponding callback() will never fire. The callback() receives as arguments, the previous and current value returned by the getter() (not of the changed reactive property).

Explanation:

Without knowing any internals of watchEffect(), just based on some testing, I suspect, that whenever a reactive property is accessed, vue code inspects the stack-trace and if it sees that the access was initiated by a callback registered with watchEffect(), it registers that callback to be run once when the specific property changes (this regisration could even be performed with watch(ref,callback, {once:true}))

Since the property-change will trigger again the above callback(), the registration will be repeated.

The above means that if one of the executions of the callback() passed to watchEffect() does not read a reactive property, the callback() will never be executed again when the specific reactive property changes (unless some other reactive property fires the callback and inside that execution the above reactive property is accessed again).

On the other hand: The use of watch(refsArray, callback) will register the property change listener to the passed properties once and for all. Now if watch is used with in the form watch(getter, callback) the functionality is more close to watchEffect().

Practically the question should be: What is the difference between watching for reactive property changes explicitly (directly passing them) vs implicitly (passing a function that reads them). The behavior of the latter approach may cause some confusion and questioning on why some watchers stop reacting to changes.

Consider the following two cases. In Case1, watch mechanism will at some point stop working (when the randomly-generated value is < 0.3). The same for Case3 but with the decision being on getter()-side instead of callback()-side:

(Open the console and see what is printed when you write something to the input box. Try refreshing if the watch stops at the first keyup. As said in cases 1 and 3 watching will at some point stop.)

Case1 (click to test):

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

const msg = ref('Hello World!')

watchEffect(()=>{//callback
  let v = Math.random();
  console.log(`is ${v} > .3?`, v > .3);
  if(v > .3){
    console.log(msg.value)
  }else{
    console.log("watching of msg.value stops here, because the property was not accessed from inside the callback() passed to watchEffect(callback).");
  }
});

</script>

<template>
  <h1>{{ msg }}</h1>
  <input v-model="msg" />
</template>

case2 (click to test)

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

const msg = ref('Hello World!')

watch([msg],()=>{//callback
  let v = Math.random();
  console.log(`is ${v} > .3?`, v > .3);
  if(v > .3){
    console.log(msg.value)
  }else{
    console.log("watching of msg.value will not stop even though the property was not accessed inside the callback() passed to watch(arr, callback).");
  }
});
</script>

<template>
  <h1>{{ msg }}</h1>
  <input v-model="msg" />
</template>

Case3 (click to test)

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

const msg = ref('Hello World!')

watch(()=>{
  let v = Math.random();
  console.log(`is ${v} > .3?`, v > .3);
  
  if(v > .3){
    console.log("value read from getter:", msg.value);
    return Math.random();
  }else{
    console.log("watching of msg.value stops here, because the reactive property was not accessed from inside the getter() passed to watch(getter, callback).");
  }
}, 
(current, prev)=>
    console.log("value:", msg.value, "currentFromGetter", current, "prevFromGetter", prev)
);
</script>

<template>
  <h1>{{ msg }}</h1>
  <input v-model="msg" />
</template>

With the above I don't anyhow imply that watchEffect(callback) or watch(getter, callback) have any issues or bad design. Just that, in order to use them, it is good to know how they work, to avoid pitfalls.

Upvotes: 1

Xinchao
Xinchao

Reputation: 3541

watchEffect is something introduced in Vue3 with its composition api. The reason to have both watchEffect and watch, as I understand, is to keep the semantics of watch as close as possible to that of Vue2. The birth of watchEffect, if you are interested, can be traced back to here and here

As it stands today, watchEffect is an immediate/eager watch that uses a more concise/consistent syntax (consistent with computed):

  1. watchEffect does not accept explicit watch sources, but instead automatically figures out all the dependencies by immediately executing the callback (or effect as how it is called in the source code), similar to how computed works. Therefore watchEffect must run the effect immediately. And because of this, there is a common trap (at least I have to keep reminding myself of it constantly) when setting up watchEffect: you need to make sure that during the first execution of your watchEffect, all of the dependencies are indeed accessed. How would some dependency escape from being accessed? Watch for conditional statements.
  2. watchEffect will run its effect immediately as mentioned above.
  3. watchEffect is a deep watch. This is something I am not sure whether it is intended or not. If you use a reactive object inside your effect, any change on that object will cause the effect to rerun, even if the changed property is not the one you accessed or is nested.

If Vue 3 is designed from scratch or there is no concern of maintaining backward compatibility, I would imagine there will only be watchEffect

Upvotes: 15

moriartie
moriartie

Reputation: 1126

What helped me to understand the difference between watch and watchEffect in Vue 3 was to think about watchEffect as computed with side-effects.

The watchEffect() hook works like the computed() hook or the computed option, but instead of returning a value, you use it to trigger side-effects.

Use watch whenever you want to trigger a side-effect when a single reactive value changes.

// Triggers whenever `user` is updated.
watch(user, () => doSomething({ user: user.value, profile: profile.value }))

Use watchEffect whenever you need to watch multiple reactive values and trigger a side-effect whenever any of them is updated.

// Triggers whenever `user` *or* `profile` is updated.
watchEffect(() => doSomething({ user: user.value, profile: profile.value }))

See: watch vs. watchEffect when to use what with Vue.js

Upvotes: 28

Liang Zhou
Liang Zhou

Reputation: 2165

I would use:

  • watchEffect when I want to watch multiple reactive properties and I don't care about old values
  • watch when I want to watch one specific reactive properties and I may want old value

Note, above is what I would use them for, but may not be their only usage.

Also found in docs regarding the difference:

Compared to watchEffect, watch allows us to:

Perform the side effect lazily;
Be more specific about what state should trigger the watcher to re-run;
Access both the previous and current value of the watched state.

Source: https://composition-api.vuejs.org/api.html#watch

Upvotes: 10

JaredMcAteer
JaredMcAteer

Reputation: 22535

watchEffect seems to be a simplified watch and the main differences are

  • Only accepts a function
    • watch can accept either a function or one or more reactive properties.
  • Runs immediately when defined and when reactive dependencies change
    • watch only runs when reactive dependencies change

Upvotes: 14

Related Questions