Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Vue.watch - Watchers for custom objects. #9509

Closed
Aferz opened this issue Feb 17, 2019 · 14 comments
Closed

Vue.watch - Watchers for custom objects. #9509

Aferz opened this issue Feb 17, 2019 · 14 comments

Comments

@Aferz
Copy link

Aferz commented Feb 17, 2019

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.

@Aferz
Copy link
Author

Aferz commented Feb 17, 2019

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).

@sirlancelot
Copy link

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)
    })
  }
})

@Aferz
Copy link
Author

Aferz commented Feb 17, 2019

@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.

@sirlancelot
Copy link

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.

@Aferz
Copy link
Author

Aferz commented Feb 18, 2019

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

Why do you consider this? Just curious.

@sirlancelot
Copy link

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.

@Aferz
Copy link
Author

Aferz commented Feb 18, 2019

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

@posva
Copy link
Member

posva commented Feb 18, 2019

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

@posva posva closed this as completed Feb 18, 2019
@yyx990803
Copy link
Member

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.

@Aferz
Copy link
Author

Aferz commented Feb 18, 2019

Awesome!

Thanks, @yyx990803 & @posva

@Jaimeloeuf
Copy link

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! 🙏🏻

@Aferz
Copy link
Author

Aferz commented May 31, 2020

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

@lcherone
Copy link

lcherone commented Jul 9, 2021

Thought id share here as this thread helped, this is how I accomplished saving encrypted state on change into localstorage.

Essentially boils down to:

inject('state', new Vue({
  data: () => ({
    state: {
      something: ''
    }
  }),
  watch: {
    state: {
      handler (value) {
        // do something
      },
      deep: true
    }
  }
}).$data.state)

i.e. When this.$state.something is changed, its reactive and the vue instance handles firing off the watcher.

import Vue from 'vue'
const CryptoJS = require('crypto-js')
const obfusck = CryptoJS.SHA256('some-obfuscation-key').toString()

export default ({
  app
}, inject) => {

  let data = {
    state: Vue.observable({
      something: 'foo bar baz'
    })
  }

  if (process.browser) {
    // eslint-disable-next-line no-prototype-builtins
    if (typeof window.localStorage !== 'undefined' && window.localStorage.hasOwnProperty('state')) {
      try {
        data = JSON.parse(CryptoJS.AES.decrypt(window.localStorage.getItem('state'), obfusck).toString(CryptoJS.enc.Utf8))
      } catch {}
    }
  }

  inject('state', new Vue({
    data: () => data,
    watch: {
      state: {
        handler () {
          if (process.browser) {
            if (typeof window.localStorage !== 'undefined') {
              window.localStorage.setItem('state', CryptoJS.AES.encrypt(JSON.stringify({
                state: this.state
              }), obfusck))
            }
          }
        },
        deep: true
      }
    }
  }).$data.state)
}

@m3di
Copy link

m3di commented Jan 24, 2023

just wanted to share after some searching:

state = Vue.observable({ someValue: '123' });

new Vue({
    computed: {
        state: () => state
    },
    watch: {
        state(v) {
            console.log('value changed to ' + v.someValue);
        }
    }
});

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

7 participants