Limited set of reactive props for a data nested object

Hi, I am progressively adding Vue stuffs and converting things in a legacy app.
When creating components and defining its data, it would be great if we could specify a set of properties names to be watched by Vue for a specific nested object, instead of having Vue automatically and recursively observe all its properties.
Indeed observing a whole big object, sometimes containing things that we are not aware of, can cause performance issues. I experienced that with an object referencing THREE JS (meshes) stuffs, and to solve performance issues I had to create a tiny state sub-object on the big object, that I referenced in the data of my Vue component instead of the initial big object.
I guess this is the way to go, and its seems cleaner, but in the context of a progressive conversion to Vue, I don’t necessarily have time to refactor things and do such modifications for the moment.

What would seem very helpful in my case would be something like:

import { PartialObserver } from 'vue'

export default {
  data () {
    return {
      // only make some properties reactive on bigOject
      foo: PartialObserver(bigObject, ['interestingPropA', 'interestingPropB', 'bar.interestingDeeperProp']),
    }
  }
}

Maybe I could obtain a similar behavior and such a feature is already existing ?
I hope you understand my use case.

Thanks and regards.

Something like this?

  watch: {
    'ObjectName.propertyName': {
      handler: 'doSomethingUsefulFunction' 
    },
    'ObjectName.propertyName.childPropertyName': {
      handler: 'doSomethingElseThatIsUsefulFunction' 
    },


  },

But in this case, I thought ObjectName would have to be in the data, so it would entirely be observed by Vue ? or am I mistaken and ObjectName can be in the globals ? or defined as this.ObjectName in created for example ?

Thanks

It doesn’t have to be in the data, it could be a prop passed into the component or a property of a state object contained in a VueX store.

It pretty much can be anything on the Vue instance

Interesting !
I’m trying, but when I do this:

beforeCreate () {
  this.myObj = myBigObj
},
watch: {
  'myObj.foo': {
    handler (val) {
      console.log('foo changed to', val)
      this.foo = val
    }
  },
},

Nothing happens when I change the value of myBigObj.foo, the handler is not called.
Hmm I guess the way I do it is not correct ? :thinking:
(but it work if I put myObj: myBigObj in data, which is what I want to avoid)

Try this syntax

    'myObj.foo': function (newVal, oldVal) {
      if (newVal !== oldVal) {
           console.log('foo changed to ' + newVal + 'from' + oldVal)
           this.foo = newVal
      }
    }

This is the same, the handler is never called, except if I put myObj in data, which is what I want to avoid.
I tried to define it as a default value of a prop in props, but it did not work.

Thanks

Hmmm sorry it didn’t work for you. I will have a think and maybe by then someone else will have provided a solution!

Good luck.

Thank you for your time.
The doc https://vuejs.org/v2/api/#watch only shows things available in data, that is why I didn’t think it was possible with something not in data, but I hope you are right.

I have done it with props and with the VueX store as I am looking at the working code right now so maybe that will help you with a solution.

I think actually you haven’t added it to the Vue instance, to do that you need to use

Vue.prototype.$myObj= myObj

Then when you console.log (this) you should your new $myObj property is there somewhere in the tree for your component.

This might also help

Hmm I don’t have a this.$myObj property on the component if I do what you suggest :confused:
I don’t use VueX, maybe it makes your object reactive under the hood, like if it was in the data of the component.
I also tried to call the this.$watch API but without result.

Sorry forgot to add. It will be in one of the nested prototype properties of the tree. Console.log(this) and open your proto if it isn’t there, open that ones proto too. You should find it somewhere there.

It should exist as this.$myObj so you can try and console.log that out. Then you should be able to watch one of its properties for a change.

Ok indeed this.$myObj exists, but modifying the object does not trigger any handler (I tried watching 'myObj.foo' or '$myObj.foo').

Maybe someone knows what I want to obtain is not possible out of the box without Vuex ?
Thank you for the help

Just a guess, sorry don’t have the time to check it, try to put properties of your big object into data selectively like

export default {
  data () {
    return {
      interestingPropA: bigObject.interestingPropA,
      interestingPropB: bigObject.interestingPropB,
      interestingDeeperProp: bigObject.bar.interestingDeeperProp
    }
  }
}

Well you could use Vue.observable() to make your partial object reactive, but then you can only use it in render functions or computed properties.

Here an example:

import Vue from 'vue';

const myBigObj = {
  foo: {
    bar: 0
  }
};

myBigObj.foo = Vue.observable(myBigObj.foo);

new Vue({
  render(h) {
    return h('button', {
      on: { click: () => { myBigObj.foo.bar++ }}
    }, `count is: ${myBigObj.foo.bar}`)
  }
}).$mount('#app');

Another option would be adding your partial object in the data property of Vue.

import Vue from 'vue';

const myBigObj = {
  foo: {
    bar: 0
  }
};

new Vue({
  template: `<button @click="myPartialObj.bar++">Change myObj</button>`,
  
  data() {
    return {
      myPartialObj: myBigObj.foo
    }
  },

  watch: {
    'myPartialObj.bar'(val) {
      console.log('Changed myPartialObj.bar to: ' + val);
    }
  }
  
}).$mount('#app');

Let me know, if it’s fits your needs. Otherwise we can look for another option. :slight_smile:

FYI, only reactive data will trigger a watch handler. In this case, you’ve only added the object to the instance object so it’s not reactive.

There isn’t a built in way to limit what Vue makes reactive in an object. It’s a bit of an all or nothing situation, atm. Of course you could freeze parts of an object, but then their values are… well, frozen, so if you need to change them then you’ll have to clone the object to mutate it and then re-freeze it.

https://jsfiddle.net/jamesbrndwgn/gcy06qbw/

In my case the interesting properties are simples values like booleans, numbers… and not objects themselves, so I guess for eg. mutating bigObject.interestingPropA afterwards would not trigger a change of data.interestingPropA ? As data.interestingPropA would have just been initialized copying the value of bigObject.interestingPropA. Or I’'m wrong ?
In all cases thank you for the suggestion.


I guess it could work but what I forgot to mention is that the interesting properties for me are most of the time just simples values like booleans, strings, numbers… at the root of the big object.
So I can’t isolate a myBigObj.foo sub-object.
Thanks

Thanks, that is what I thought after some struggle :wink: In my case freezing the big object is not an option, as it is used and updated by other parts of the application.
In my case it is important to keep in mind that I am progressively migrating to Vue, and I have to deal with existing stuffs without necessarily having the time to refactor lots of things.

The possibility to specify a list of props / nested props to observe on an object instead of a whole object would be a great new feature I think, especially for optimization purposes.
In my mind Vue is recommended for eg. over React for projects where you want a progressive migration towards a modern component-based framework, and in this context it would help (but I don’t know if React already has the feature I need… in any case too late for me to change :wink: And I like Vue).

I tried to check whether I could use a Proxy object for my need, but I don’t think it could easily help me.

For the moment the better way to address my need was to manually add getters/setters on the big object, that write the values in a small bigObject.state object. Then in my Vue JS components I just put state: bigObject.state in the components data instead of the whole bigObject.
But I am not satisfied with that, as these are specific modifications of the original objects just for the need of Vue, and adds some noise and inconsistency in the original code, especially as I must keep things backward compatible.

Thanks

Maybe we could discuss about the addition of such a feature ? (it would certainly be for advanced usages and documented as so).
In the legacy app I’m working on, there are several class instances living and updated in multiple places, and being able to progressively add new Vue components on top of that, without refactoring and with performance in mind, so only observing the needed properties, would be awesome.