by Odin Ravensson on
Vue watchers are essential for handing side effects on the state of data in Vue components.
// Reactive objects, direct or computed references
watch(foo, (newFoo, oldFoo) => {});
// Getters
watch(() => foo.bar, (newBar, oldBar) => {});
// Will not correctly watch a property of a reactive object
watch(foo.bar, (nb, ob) => console.info('Will never fire'));
Watchers automatically track the entire tree of a reäctive object. However, when using a getter to a property, the watcher will only fire if the property changes, meaning updates to the property’s own properties will not fire by default.
watch(() => foo.bar, () => console.info('Only fires when `bar` is replaced'));
watch(() => foo.doop, () => console.info('Now will fire if one of `doop`\'s properties is updated'), { deep: true });
As of 3.5, deep’s value may be an integer indicating how many levels down the watched
property tree to track. Beware of performance issues when watching large or deep values.
Consider using deep: false or deep: 1.
Watchers will—by default—only update when the watched value updates. One can force an
immediate response by using immediate: true in the third parameter, the WatchOptions.
By using the option once: true, the callback will fire the first time the value updates
and no other.
Nota Bene: the WatchOptions also has optional properties
onTrack(event: DebuggerEvent) and onTrigger(event: DebuggerEvent) which are only used in
development mode. onTrack will fire when Vue begins watching the value, and onTrigger
will run each time the value is updated.[^1]
watchEffect() is similar to watch() except that the callback is the first parameter, and
the watched input is omitted. It will automatically track any of the reactive values in
the callback method—or at least until the first await[^2].
Watchers have three flush timings available in the WatchOptions; in run order they are
'sync', 'pre' and 'post'. 'pre' is the default and in this timing, Vue will combine
all the changes of the value in the tick and will execute a single callback before updates
are made to the DOM. Because it happens before the DOM updates, one will not have access to
the owner component’s DOM. 'post' happens after the DOM updates, allowing access to the
owner’s DOM. 'sync' runs whenever the value updates; this can cause significant
performance issues. watchEffect has a convenience synchronous version: watchSyncEffect.
Because asynchronous execution may need to be canceled, there are hooks for that, either
through the imported onWatchCleanup() method or the cleanup function passed as the third
parameter in the callback function. Either of these take a closure and must be called in the
synchronous portion of the callback. Use them to signal requests to stop or to exist early
from synchronous functions. It works very well with an AbortController.
Watchers are automatically cleaned up if they are created synchronously with the component, but if they are created out of sync, they will need to be cleaned up manually to avoid a memory leak. This is done by calling the .stop() method on the object returned by watch() and related methods. This object also has .pause() and .resume(). These might be useful for reducing computation needs.
[^1] computed() has the same options too.
[^2] I assume anyway. Judging by Vue’s other call-outs of asynchronous code losing the
context after the first await, I’d imagine this to be the case. It might merit a probe.