Move watch and watchEffect API to @vue/reactivity
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.
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()
@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()