Vue: Vue.watch - Watchers for custom objects.

Created on 17 Feb 2019  ·  12Comments  ·  Source: vuejs/vue

What problem does this feature solve?

More than a problem this is a PoC. I'd like to test if this feature is something wanted or even if it has sense inside Vue ecosystem.

With this proposed API, Vue would have the ability to create watchers over custom objects that aren't part of a component.

What does the proposed API look like?

The definition of this method would be something like:

Vue.watch(obj: Object, expOrFn: string | Function, cb: any, options?: Object): Function

The usage of this API would be something like:

const state = {
  counter: 0
}

// Use subscriber() to unsubscribe ...
cons subscriber = Vue.watch(state, 'counter', newVal => console.log(`Counter updated ${newVal}`))

state.counter += 1

// Console output: 1

Take into consideration

Current implementation of Watcher

The Watcher object requires a Vue instance. I don't know if its mandatory for a watcher but I'd like to know if it feasible to decouple the Watcher from the need of an instance. Checking the code I've seen the VM is only responsible for storing the watchers and bind itself into the callback functions.

This seems to be a considerable amount of work.

Other thing would be that the passed object would _need_ (Maybe it's not needed) to store its watchers somewhere. Maybe a private property (__watchers__) inside the current object. Ideas are welcomed.

To be or not be reactive

The object passed to Vue.watch could be reactive or non-reactive. I see 2 options here:

  1. Only allow reactive objects using Vue.observable first. With this approach, the initial example won't work because the state was a plain object.
const state = Vue.observable({
  counter: 0
})

Vue.watch(state, 'counter', newVal => console.log(`Counter updated: ${newVal}`))

state.counter += 1

// Counter updated: 1
  1. Vue.watch makes your object reactive, if it isn't initially. We won't need to make it observable, because Vue.watch make it reactive for us. This approach will mutate the original object.

Final note

I didn't make an RFC because I want to check if this feature is useful and feasible before I inspect a bit more in the details of this new API.

If core maintainers think that it will be more suitable in RFCs repository, I'll write one then.

Any feedback is welcomed.

Thanks.

Most helpful comment

We do plan to expose something like this in v3. There will be an RFC discussing the actual API design, and when it is finalized we will likely introduce it in v2 first.

All 12 comments

I'd like to mention that this behaviour is currently possible in userland with something like:

function watch(obj, expOrFn, cb, opts) {
  let instance = null

  if ('__watcherInstance__' in obj) {
    instance = obj.__watcherInstance__
  } else {
   instance = obj.__watcherInstance__ = new Vue({ data: obj })
  }

  return instance.$watch(expOrFn, cb, opts)
}

const state = {
  count: 0
}

const subscriber = createWatcher(state, 'count', count => console.log(`Counter updated ${count}`))

But this solution has its "problems":

  1. It creates a "dummy" instance of Vue for every object passed. It's like the Global Event Bus pattern, but for watchers.
  2. The object has to store the created instance to avoid creating even more Vue instances.

If creating lots of Vue instances is not a problem, maybe this can be a good alternative to the proposed API (But a little hacky).

This is possible now in Vue 2.6.x using the new Vue.observable() helper:

const shared = Vue.observable({ value: "hello" })

const vm = new Vue({
  created() {
    this.$watch(() => shared.value, (value) => {
      console.log("value changed to:", value)
    })
  }
})

@sirlancelot Yes, I put an example in the very first comment.

Maybe we could call it syntactic sugar, but I think the necessity of a new Vue instance could be decoupled.

Oh I understand now. Yes, I do love Vue's reactivity system. I think using it outside of components is probably an anti-pattern though.

I think using it outside of components is probably an anti-pattern though.

Why do you consider this? Just curious.

I feel like developers should focus their efforts on bringing functionality IN to Vue through the use of components. You can get data OUT of those components through watchers. However, behavior should be isolated to the components.

When I think in features like this one I like to think in environments where you only need/want the reactive features of Vue but you are not interested in components. Reactive data is such a good feature that has benefits inside and outside components or even Vue.

Anyway, thanks for sharing your point of view @sirlancelot

I would say, use the observable api, it's there for that, watching objects outside of components. In those scenarios, you are likely to put the shared object in a file and import that shared objects at multiple places if needed.
If you have access to $watch, then it would make sense to watch the component data (or others) not something from the outside. Keeping them separate will indeed prevent users to $watch anything

We do plan to expose something like this in v3. There will be an RFC discussing the actual API design, and when it is finalized we will likely introduce it in v2 first.

Awesome!

Thanks, @yyx990803 & @posva

Hi really sorry to comment on this old thread, this just seems to be the only place where this subject is talked about in depth.
Just wanted to ask if this is already available now and if not what is the status of this? Or will this only be available in Vue 3.x.x? Thank you! 🙏🏻

@Jaimeloeuf You'll have this feature in 3.0 out of the box!

Was this page helpful?
0 / 5 - 0 ratings