Computed and Watch

    Sometimes we need state that depends on other state - in Vue this is handled with component computed properties. To directly create a computed value, we can use the method: it takes a getter function and returns an immutable reactive object for the returned value from the getter.

    Alternatively, it can take an object with get and set functions to create a writable ref object.

    1. const count = ref(1)
    2. const plusOne = computed({
    3. get: () => count.value + 1,
    4. set: val => {
    5. count.value = val - 1
    6. }
    7. })
    8. plusOne.value = 1
    9. console.log(count.value) // 0

    To apply and automatically re-apply a side effect based on reactive state, we can use the watchEffect method. It runs a function immediately while reactively tracking its dependencies and re-runs it whenever the dependencies are changed.

    1. const count = ref(0)
    2. watchEffect(() => console.log(count.value))
    3. // -> logs 0
    4. setTimeout(() => {
    5. count.value++
    6. // -> logs 1
    7. }, 100)

    When watchEffect is called during a component’s setup() function or , the watcher is linked to the component’s lifecycle and will be automatically stopped when the component is unmounted.

    In other cases, it returns a stop handle which can be called to explicitly stop the watcher:

    1. const stop = watchEffect(() => {
    2. /* ... */
    3. })
    4. stop()

    Side Effect Invalidation

    Sometimes the watched effect function will perform asynchronous side effects that need to be cleaned up when it is invalidated (i.e state changed before the effects can be completed). The effect function receives an onInvalidate function that can be used to register an invalidation callback. This invalidation callback is called when:

    • the effect is about to re-run
    • the watcher is stopped (i.e. when the component is unmounted if watchEffect is used inside setup() or lifecycle hooks)
    1. const data = ref(null)
    2. watchEffect(async onInvalidate => {
    3. onInvalidate(() => {...}) // we register cleanup function before Promise resolves
    4. })

    An async function implicitly returns a Promise, but the cleanup function needs to be registered immediately before the Promise resolves. In addition, Vue relies on the returned Promise to automatically handle potential errors in the Promise chain.

    Vue’s reactivity system buffers invalidated effects and flushes them asynchronously to avoid unnecessary duplicate invocation when there are many state mutations happening in the same “tick”. Internally, a component’s update function is also a watched effect. When a user effect is queued, it is always invoked after all component update effects:

    1. <template>
    2. <div>{{ count }}</div>
    3. </template>
    4. <script>
    5. export default {
    6. setup() {
    7. const count = ref(0)
    8. watchEffect(() => {
    9. console.log(count.value)
    10. })
    11. return {
    12. count
    13. }
    14. }
    15. }
    16. </script>

    In this example:

    • The count will be logged synchronously on initial run.
    • When count is mutated, the callback will be called after the component has updated.

    Note the first run is executed before the component is mounted. So if you wish to access the DOM (or template refs) in a watched effect, do it in the mounted hook:

    1. onMounted(() => {
    2. watchEffect(() => {
    3. // access the DOM or template refs
    4. })

    In cases where a watcher effect needs to be re-run synchronously or before component updates, we can pass an additional options object with the flush option (default is 'post'):

    Watcher Debugging

    The onTrack and onTrigger options can be used to debug a watcher’s behavior.

    • onTrack will be called when a reactive property or ref is tracked as a dependency
    • onTrigger will be called when the watcher callback is triggered by the mutation of a dependency
    1. watchEffect(
    2. () => {
    3. /* side effect */
    4. },
    5. {
    6. onTrigger(e) {
    7. debugger
    8. }
    9. }
    10. )

    onTrack and onTrigger only work in development mode.

    The watch API is the exact equivalent of the component watch property. watch requires watching a specific data source and applies side effects in a separate callback function. It also is lazy by default - i.e. the callback is only called when the watched source has changed.

    • Compared to , 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.

    A watcher data source can either be a getter function that returns a value, or directly a ref:

    1. // watching a getter
    2. const state = reactive({ count: 0 })
    3. watch(
    4. () => state.count,
    5. (count, prevCount) => {
    6. /* ... */
    7. }
    8. )
    9. // directly watching a ref
    10. const count = ref(0)
    11. watch(count, (count, prevCount) => {
    12. /* ... */
    13. })

    Watching Multiple Sources

    A watcher can also watch multiple sources at the same time using an array:

    1. watch([fooRef, barRef], ([foo, bar], [prevFoo, prevBar]) => {
    2. /* ... */
    3. })

    watch shares behavior with in terms of manual stoppage, (with passed to the callback as the 3rd argument instead), flush timing and .