Subscribe on changes!

Move watch and watchEffect API to @vue/reactivity

avatar
Aug 21st 2021

What problem does this feature solve?

Listeners (that is, watch and watchEffect functions) are an important part of the Vue.js responsive system. Since Vue.js 3, most of the reactive functions (such as reactive, ref, effect, etc.) have been extracted into the brand new @vue/reactivity package, but they are in @vue/runtime-core package. The watch and watchEffect functions are also part of the reactive system, and they should be located in the @vue/reactivity package. For ordinary users, there will not be any functional changes. But for library authors, if they need them, they must be re-implemented (because it is impossible to import the @vue/runtime-core package). On the contrary, if they are in the @vue/reactivity package, things will be much simpler.

Implementation

We can extract these two functions into @vue/reactivity, remove the Vue.js specific code, and re-add these codes in packages/runtime-core/src/apiWatch.ts, we can to add them around the function (such as the flush option), it is not difficult.

Notes

#858 also proposed this, but it was closed. We can use (@vue-reactivity/watch)[https://github.com/vue-reactivity/watch], but it is too old, please see (Watch with @vue/reactivity)[https://antfu.me/posts/watch-with-reactivity].

What does the proposed API look like?

It should look like this:

import { ref, watch, watchEffect } from '@vue/reactivity'
let count = ref(0)
watch(count, (count, prevCount) => console.log('old:', prevCount, 'new:', count))

count.value = 1 // old: 0 new: 1

let msg = ref('hello')
watchEffect(() => console.log('msg:', msg.value)) // msg: hello
msg.value = 'world' // msg: world

Its usage is the same as before, but the flush option will not be available.

avatar
Aug 21st 2021

I think exposing two functions with the same name in two different packages is prone to errors, especially for IDEs as they can import the wrong version and this is problematic because their behavior is not the same, ending up in very difficult to debug errors that are very easy to encounter (@vue/reactivity is installed alongside vue). As you pointed out at https://antfu.me/posts/watch-with-reactivity, you can implement this yourself or directly use effect()

avatar
Sep 3rd 2021

@posva I am also interested in this issue.

I know there were two computed in vue. One is inside @vue/reactivity and another one is inside @vue/runtime-core.

With the help of EffectScope, there is only one computed inside @vue/reactivity now.

So I am curious if there is a way to move watch to @vue/reactivity just like computed.

Then we can write code like this outside vue.

// effect, computed, watch, watchEffect created inside the scope will be collected

const scope = effectScope()

scope.run(() => {
  const doubled = computed(() => counter.value * 2)

  watch(doubled, () => console.log(doubled.value))

  watchEffect(() => console.log('Count: ', doubled.value))
})

// to dispose all effects in the scope
scope.stop()