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

Limited set of reactive props for a data nested object #10265

Closed
colin-guyon opened this issue Jul 14, 2019 · 6 comments
Closed

Limited set of reactive props for a data nested object #10265

colin-guyon opened this issue Jul 14, 2019 · 6 comments

Comments

@colin-guyon
Copy link

colin-guyon commented Jul 14, 2019

What problem does this feature solve?

Referencing a big object in the data of a component can lead to performance issues: indeed Vue automatically recursively observes all properties found in the object.

This is not ideal to reference big objects that are not just pure data or states, but especially in the context of a progressive migration of a legacy application to Vue (ie. adding new components on top of an existing architecture), one can have parts of existing big objects to observe for components to react seamlessly.

I had performance issues when referencing an object having itself references to Three JS stuffs (meshes...): interacting with the 3D scene was much slower after the instantiation of the Vue component, whereas I just needed some properties at the root of this object to become reactive.

Thus I suggest adding the possibility to specify a limited set of properties to observe for an object referenced in the data of a component.

A good practice in such case is certainly to isolate the needed properties inside a much smaller object without uncontrolled content, but in the context of a progressive migration, refactoring things cannot always be an option.
Copying and/or partially freezing the object is not an option in my case as it needs to stay mutable for the existing application to keep working.
In my case the interesting properties being of simple types (String, Number, boolean...) and being at the root of the big object, the entry point is the big object itself.

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 adding this feature would help.

(Previously discussed here: https://forum.vuejs.org/t/limited-set-of-reactive-props-for-a-data-nested-object/)

I could work on a PR, even if I don't have a lot of time, but for the moment this is just a feature suggestion :)

What does the proposed API look like?

// src/someLegacyModule.js
export const bigOject = {
  propA: ...,
  propB: ...,
  bar: {
    deeperProp: ...,
    // lots of stuffs we don't need in our Vue components
  }
  // lots of stuffs we don't need in our Vue components
}

Initially I thought of something like the following, but it could be considered as not totally consistent with the framework as an import is needed:

import bigObject from '@/src/someLegacyModule'
import { PartialObserver } from 'vue'

export default {
  template: `
    <div>
      {{ foo.propA }}
      <div v-if="foo.propB">{{ foo.bar.deeperProp }}</div>
    </div>
  `,
  data () {
    return {
      // only make needed properties reactive on bigOject (as it could be huge and very deep)
      foo: PartialObserver(bigObject, ['propA', 'propB', 'bar.deeperProp']),
    }
  }
}

Or maybe something like this, without additional import:

export default {
  data () {
    return {
      foo: {
        // only make needed properties reactive on bigOject (as it could be huge and very deep)
        __partialObserver: [bigObject, ['propA', 'propB', 'bar.deeperProp']])
      }
    }
  }
}

The name of the added special property where we can define the limited list of properties to make reactive could be different: '_observeOnly', '_watchOnly', '_reactiveProps' ... ? Maybe adding a $ or _vue prefix (but I know this is more for Vue internals) ?

It could also be separate, as not adding an extra level of indirection may be easier to implement:

export default {
 data () {
   return {
     foo: bigObject,
     __settings: {
       foo: {
         watchOnly:  ['propA', 'propB', 'bar.deeperProp']
       }
     }
   }
 },
 // ... or instead of the above:
 // dataConfig: {
 //   foo: {
 //     watchOnly:  ['propA', 'propB', 'bar.deeperProp']
 //   }
 // },
}
@posva
Copy link
Member

posva commented Jul 14, 2019

Thanks for the proposal. To make things non reactive you can use Object.defineProperty or Object.freeze if you don't want to modify the object anymore which is also a simple straight call to

Here is an example using Object.defineProperty and Object.freeze: https://jsfiddle.net/posva/fdruqp3v/

This will allow you to create your PartialObserver function or even a plugin to handle a nonReactive property. There is also https://github.com/rpkilby/vue-nonreactive which uses a slightly different solution

@posva posva closed this as completed Jul 14, 2019
@colin-guyon
Copy link
Author

Thanks for your jsfiddle and suggestions.
However, in the case I tried to describe, freezing the object is not possible.

If I could use Object.defineProperty to make Vue ignore almost everything on a given object, except the few properties I want to be reactive, it would be OK. But all the modified properties should stay updatable like before, so not freezed. Is it possible ?
Maybe using Object.defineProperty to modify existing properties is also a bit intrusive, but it is interesting.

vue-nonreactive is interesting too, but if I'm not mistaken it does not address the case of values of simple types at the root of an object: how do you choose to let obj.propA and obj.propB be reactive but not obj.propC etc (as we cannot assign a .__ob__ to numbers, strings or booleans) ? (If I could do that it could suit my needs indeed).
Also "duping Vue's observer detection" is not really future proof...
but I understand the idea of keeping the Vue core as thin as possible by not handling all needs.

Regards.

@posva
Copy link
Member

posva commented Jul 14, 2019

But all the modified properties should stay updatable like before, so not freezed. Is it possible ?

Yeah, with writable as in the fiddle

I wouldn't go for vue-nonreactive method either as it is using Vue private API

@colin-guyon
Copy link
Author

The only way I found to prevent Vue from making an existing property of an object reactive is to use Object.defineProperty and set configurable to false:

const extra = {
  existingAndIninteresting: 'a'
}
Object.defineProperty(extra, 'existingAndIninteresting', {
  configurable: false,
})

I guess it would be the way to go.
Just setting writable to false does not prevent the property from becoming reactive.
(in your example you add the property, but in my case I can only modify an existing property)

@colin-guyon
Copy link
Author

colin-guyon commented Jul 14, 2019

But unfortunately it seems it does not work if the property has a getter/setter :(
I'm also questioning about the ability of this technique to work when Vue will use Proxies.

@colin-guyon
Copy link
Author

#2637

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

2 participants