-
-
Notifications
You must be signed in to change notification settings - Fork 33.7k
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
[feature] Ability to disable Vue observation #2637
Comments
Won't |
|
|
Then maybe it's time to rethink your model design. Why nest those things under something to be observed? |
Because the cache is used to dynamically lookup related resources. eg, we could have calling author.posts gets the posts from the cache. |
By design, Vue discourages putting complex objects with their own state-mutating mechanism into Vue instance's |
First, clarifying question - what exactly do you mean by pure state? We have two types of state:
But anyway: <!-- layout -->
<post :post="post"></post>
<author :author="author" ><author>
<comments :comments="comments"></comments> import post from 'components/post';
import author from 'components/author';
import comments from 'components/comments';
/* post = {
* template: '...'
* props: ['post'],
* data: () => {collapsed: false},
* ...
* }; */
new Vue({
el: 'body',
data() {
instance = postStore.fetch({include: ['author', 'comments.author']})
Vue.nonreactive(instance.cache)
return {post: instance, },
},
components: {
post,
author,
comments,
},
...
}); Basically, we have a parent vue responsible for placing reusable components in a layout and fetching/binding data to the associated components. The child components don't fetch their own data because the data is different in different contexts. eg, a list of a user's comments vs. a list of a post's comments. The model is fairly 'dumb' with the exception that related objects are not nested ( Also, not that the request is relevant any more, but an alternative might be to not observe 'private' object members. This could be members with a leading single or maybe double underscore. Downside to this approach is that it would be breaking change. |
If anyone ends up needing this functionality, I've released it as vue-nonreactive with the appropriate admonitions and everything. |
@rpkilby thanks for sharing! |
@rpkilby One way I copy an object and remove the observable/reactivity var newObj = JSON.parse(JSON.stringify(obj)) Really useful since I want to keep an array of "states" and implement a state history object in vuex. Edit: This solution was specific to my case. I had an object where I only needed a copy of property values at a moment in time. I did not care about references, dynamic updates etc. |
Right now freezing the object is not a long-term solution. Vue-nonreactive has Vue as a dependency which is overkill when it comes to doing something that straight forward. Wouldn't a simple check in the code such as class Unobservable {
construtor() {
Object.defineProperty(this, '__ob__', {
enumerable: false, configurable: false,
writable: false, value: false,
});
}
} This is mostly an issue for libraries used in Vue applications (at least for me). |
How to tell Vue to only watch (defineProperty to) the 1-level depth of data? My case is, I want Vue to get notified when I used to use Do I have to do the below? data () {
return {
wrapper: Object.freeze({
actual: [bigData]
})
}
},
methods: {
operation () {
this.wrapper = Object.freeze({
actual: [newBigData]
})
}
} // core/observer/watch.js
function _traverse (val: any, seen: ISet) {
let i, keys
const isA = Array.isArray(val)
if ((!isA && !isObject(val)) || !Object.isExtensible(val)) {
return
}
// ... // core/observer/index.js
export function observe (value: any, asRootData: ?boolean): Observer | void {
if (!isObject(value) || value instanceof VNode) {
return
}
let ob: Observer | void
if (hasOwn(value, '__ob__') && value.__ob__ instanceof Observer) {
ob = value.__ob__
} else if (
observerState.shouldConvert &&
!isServerRendering() &&
(Array.isArray(value) || isPlainObject(value)) &&
Object.isExtensible(value) &&
!value._isVue
) {
ob = new Observer(value)
}
// ...
|
You can use destructuring var newObj = { ...obj }; |
This should fix it. It will make the isPlainObject method return false. /**
* Makes an object and it's children unobservable by frameworks like Vuejs
*/
class Unobservable {
/**
* Overrides the `Object.prototype.toString.call(obj)` result
* @returns {string} - type name
* @see {@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Symbol/toStringTag}
*/
get [Symbol.toStringTag]() {
// Anything can go here really as long as it's not 'Object'
return 'ObjectNoObserve';
}
} >> Object.prototype.toString.call(new Unobservable());
"[object ObjectNoObserve]" |
Hi all, one point that has been lost in the responses is that the data in the original comment is not pure state. In my case, I have a model instance with a private reference to a relationship lookup cache. For example, an
In response to some suggestions:
My only criticism:
I'm not sure why this would be a bad thing? You're already using Vue in your application, and the plugin is specific to Vue. If I'm not mistaken, most build tools are smart enough to not create bundles with duplicate dependencies. Regardless, this isn't correct. There is a dev dependency, but not a runtime dependency. Anyway, I'm glad to see this post has garnered some interest, and I'm sure some of these other solutions will work in a variety of other cases. I simply want to highlight the requirements from my original comment and why the suggestions aren't suitable alternatives for that case. |
So, I recently ran into this, and discovered that there's a much easier way to short-circuit Vue's observation logic: Define a property as non-configurable. Background In my application, I have to work with a 3rd party library (OpenLayers), which creates class instances that hold data, and doesn't support any reactivity system. Trying to shoehorn one in has caused so many headaches, let me tell you. So, the only viable solution for a large scale application using this library is to let OpenLayers have things the way it wants, and for me to make Vue play nicer with these horribly nested, uber objects of doom. Prior to finding this issue, my application was using about 3 gigs of ram (on our largest dataset), all of it caused by Vue making these objects reactive. Also, it was really slow when loading. I tried Vue-nonreactive, and it helped, but only to get us down to about 1 gig. Prior to using Vue, the application was sitting around 350megs. Solution Anything you don't want to be reactive, simply mark as Object.defineProperty(target, 'nested', { configurable: false }); (This stops the That's it! No Vue dependency, and arguably not even incorrect. With that, my application is down to 200megs with our largest dataset. It's simple, and easy, and requires only a documentation change on Vue's side to make it an 'official' way of making something non-reactive. |
Interesting - definitely seems like a viable alternative. |
Is there a way to temporarily pause the observation reactive and unpause it later? I have a prop watcher, within that I update a huge object where I don't want to trigger the DOM update only after the whole data preparation is finished. |
@intijk Not exactly. See, it depends on what you're trying to do; Vue eventually has to apply your state, so simply pausing while it's calculated doesn't help much. If, instead, you're trying to skip intermediate states, and only apply the final state, just start with a new object, and then assign that object at the end. For example (psuedocode): doUpdate()
{
const state = _.cloneDeep(this.myState);
// Do intermediate state updates
this.myState = state;
} (Normal Vue caveats about object reactivity apply.) My recommendation would be to use the above |
@Morgul I already used this trick for long, but the fact is that I no longer want to use this trick anymore. |
@intijk That sounds incredibly complex for something to bind Vue to. What's the use case here? |
@Morgul |
Anyone have thoughts on defining a non-reactive field within a computed property? My first idea depends on non-reactivity of assignment to array...
But that's not exactly self-documenting :-) |
Hey guys. I just found this issue and found that I'm facing a problem which is pretty like rpkilby's problem: my project construct a series of Vue Virtual DOMs(or called vnode) from a JSON object. I will use this JSON object to construct a android app. Anyway, this JSON object can be of great size, and when I use this JSON in Vue, it will be observed by Vue. I tried the rpkilby's and Morgul's way, but it doesn't work.(BTW I am in a team, while some guys may not pretty familiar with Vue, and they will probably cause the JSON observed, and my Vue Version is 2.5.16). I'm wondering if we can do this in Vue traverse: |
consider this https://github.com/vuejs/vue/blob/v2.5.16/src/core/observer/index.js#L121 Today I met a case that, Vue observe a map instance of mapbox-gl, then weird things happed, the map get lighter. But the map instance need to be passed between vue instances. After I add |
+1 to support that officially. I'm using a large object that don't need reactivity in a component, and disabling unused reactvity reduced the memory object size from 800MB to 43MB |
I've build a Vue plugin that makes it possible to make Vue variables non-reactive (it uses the beforeCreate hook). This is cleaner than vue-nonreactive - @rpkilby, please look at this comment - your solution won't work for the next version of Vue. Please look at Vue-Static for a way to make variables non-reactive. <script>
export default {
static() {
return {
map: null,
};
},
mounted() {
this.map = new mapboxgl.Map({...}); /* something heavy */
},
};
</script> |
to make vue-free.js import Vue from 'vue'
const Observer = new Vue().$data.__ob__.constructor
function prevent(val) {
if (val) {
// Set dummy observer on value
Object.defineProperty(val, '__ob__', {
value: new Observer({}),
enumerable: false,
configurable: true,
})
}
return val
}
// vue global
Vue.VUE_FREE = prevent
// window
global.VUE_FREE = prevent
// default export
export default prevent |
Figure i would give my 2 cents and solution on this. I also had similar issues implementing both the Freeze concept and fake Observer. My data comes from the server and is a recursive TreeNode scenario, My project is also using vuex in this case which added a layer to the issues seen. I constantly got I finally stepped back and wrapped my "non-reactive" properties using the classic Revealing Module Pattern this is the class (ES6/typescript) but same can be applied in plain vue as well
Computed did not work in my case cause i still needed the full object and not a handler sending me the change on the computed. At least if im understanding how a deep watcher would work against a computed subsetted result. I think taking this a next level up would be to create a injected nonrecursive helper or decorator to speed up the process. Any useful feed back would be great. |
seems like somebody solved this issue already, hope u check all the details at #4384 |
Hi, I created #10265 especially as I was not aware of this former issue. |
@colin-guyon Note that just like the new proposed A fortunate news is that, as long as you don't do much on that reactive proxy you should probably be fine. If memory hog or interoperability is a concern then you certainly don't need to worry about it since whatever the object is — a giant collection of data or some foreign object from a library which behavior is unpredictable should reactivity be applied on it, you say — nothing about it gets touched, after all. In this sense Vue 3.x actually solves a lot of corner cases where this feature would otherwise be useful. At the moment, the |
I was using this method until I got bitten by Vue dev tools bugging out because it sees The only hack I've seen with no serious side effects seems to be OP's approach with the |
Are there alternative solutions in V3 for this? |
@HunderlineK shallowRef, shallowReactive, markRaw |
I wonder why nobody mentioned here the It is more convenient when need to make entire nested objects non-reactive (in fact immutable) than This way you can plan your nested data model to be reactive as needed object by object, nest level by nest level. If you need to make only some properties immutable, @Morgul's |
Because that approach, like may others, has side effects (namely sealing the object of course, which may not be desired). We just want a way to inform Vue not to observe an object and leave it at that. If we want to seal/freeze the object then that should be up to us to do if we desire that behavior. |
This could easily be implemented by Vue maintaining a WeakSet of unobservable objects and exposing a public API to add an object to that set. Vue then simply skips observing objects in that set. The observe function isn't exported so we can't even monkey patch it if we wanted to (although I wouldn't necessarily recommend that approach either). |
We do not need this capability. We consider |
@WhereJuly In my case I tried using Consider something like this: data() {
return {
foo: new Foo()
}
} Imagine Freezing/sealing the object would break it. We could tag the object in such a way that Vue doesn't observe it. This can be done by adding a So then one solution could be to wrap the object in another which is then frozen: data() {
return {
foo: Object.freeze({
value: new Foo()
})
}
} But now we have to do But really something like this would be so much simpler: data() {
return {
foo: Vue.unobservable(new Foo())
}
} |
In our codebase we're currently using this helper for that purpose import Vue from 'vue';
// Vue doesn't publicly export this constructor
const Observer = Object.getPrototypeOf(Vue.observable({}).__ob__).constructor;
export function blockObserver (obj) {
if (obj && !obj.hasOwnProperty('__ob__')) {
Object.defineProperty(obj, '__ob__', {
value: new Observer({}),
enumerable: false,
configurable: false,
writeable: false,
});
}
return obj;
}
// example
blockObserver(new Foo()) |
@Mechazawa I'm also doing a similar thing in my codebase: const Observer = new Vue().$data.__ob__.constructor;
export function unobservable(obj) {
if (!Object.prototype.hasOwnProperty.call(obj, '__ob__')) {
// Set dummy observer on value
Object.defineProperty(obj, '__ob__', {
value: new Observer({}),
enumerable: false,
});
}
return obj;
} |
@decademoon , @Mechazawa Thanks for clarification. If I got you right you just want to watch the change of the pointer to your I do not see how it does not work. It does with either frozen or sealed object as the change of pointers in // ...
mounted() {
let count = 1;
window.setInterval(() => {
this.$data.temp = Object.freeze({ count: count });
count++;
}, 5000);
}
// ...
data(): IData {
return {
temp: Object.freeze({ count: 0 })
}
}
@Watch('temp')
onTempChanged(newValue: any): void {
console.dir(newValue);
} This works fine as follows: every 5 seconds the new object is printed in the console. You could change If I got your explanations right your use case should work fine. You replace the one frozen/sealed object with another and you get notified of it from your watch handler or in the template. |
@WhereJuly Yes your example works but at the expense of freezing the object. Freezing objects has consequences, namely none of the object's properties can be modified. Here's maybe a better less-abstract example. Assume we have a class Window {
x = 0
y = 0
moveTo(x, y) {
this.x = x
this.y = y
}
} For the sake of simplicity, I've just given it two internal properties Now imagine we want to store a reference to a data() {
return {
win: Object.freeze(new Window())
}
} I've frozen it because I want to prevent Vue from traversing through all the properties of the Now later on we call some method on the object: methods: {
moveWindow() {
this.win.moveTo(500, 500)
}
} But this throws an exception because The only way to make this work is to wrap the data() {
return {
win: Object.freeze({ value: new Window() })
}
} |
Granted, the burden of wrapping the object can be eased with something like this: data() {
return {
_win: Object.freeze({ value: null })
}
},
computed: {
win: {
get() {
return this._win.value
},
set(value) {
this._win = Object.freeze({ value })
}
}
} |
@decademoon I understand. But you use cases contradict one another. Your first use case clearly says you have a huge deep nested object you need reactivity only on its pointer, not the object's properties. I put a working solution with freeze/seal. This does exactly what it is requested to though you declared that it did not. Now. This is not the expense. This is the savings on unnecessary reactivity + unnecessary code you put in your example. Just one liner. This is it. Now in your latter post you try to use freeze/seal for the completely opposite use case where you now need to change the object. This is crystal clear freeze/seal cannot be the soultion here so why try to apply it at all. Finally this use case has nothing to do with the point of the entire thread - be able to disable Vue reactivity on objects. And as I have shown before, freeze/seal is a great simple transparent flexible and thus effective way to do this. |
I'm not sure why you think my use cases are contradicting. In my first example I never mentioned that I didn't intend to mutate the object in some way (in fact I pointed out that freezing the object would prevent me from doing so which is why it won't work), all I require that is mutating the object is allowed without triggering Vue's reactivity system (typically because of performance reasons). You're right that I only need reactivity on the pointer and not the object's properties, but freezing the object achieves this and goes one step further by disallowing mutations on the object's properties -- I still need that aspect to work! |
Maybe the misunderstanding arose from "not needing reactivity on the object's properties" - by this I mean I do not want Vue to make those properties "reactive" (you know how Vue traverses all the properties of an object and converts the properties into reactive getters and setters?). I'm using Vue's definition of "reactive", maybe you're confusing it with "mutable" (hence why you suggested freezing)? |
@decademoon I see. Then, if you need only partially mutated (by you) and reactive (notifying you changes) object you have to use different approach. First, your object has to comprise several inner objects that are given responsibilties according to the domain (as in DDD) requirements. Some of those objects (immutable, non-reactive) should be frozen/sealed for performance savings, some not (mutable, reactive). Now you may observe its changes via putting in Generally, in this use case you have to carefully select which nested objects / properties to make immutable. This is the key decision leading to performance/functional balance you have to take here. Do not forget you can freeze single properties as well as discussed above.
In our context I cannot see a reason for using mutability (be able to change an object content) with no reactivity (be able to detect those changes are made). This is the single basic reason to exist for a reactve framework.
Then just do not make it reactive. Do not put it in |
Update:
If anyone ends up needing this functionality, I've released it as vue-nonreactive with the appropriate admonitions and everything.
We have some non-plain models where we need to disable Vue's observation and walking. An example is a resource model that has access to a cache so that it can lookup related resources. This causes all of the objects in the cache to become watched (probably inefficient), as well as some additional interactions with other code. Currently, we get around this by setting a dummy Observer on the cache. Something similar to...
This does work, but
Observer
class directly.Proposal:
Add an official method for explicitly disabling Vue's observation/walking. eg, something like...
Considerations:
What should happen if a user set a reactive key path to an non-reactive object? Should vue warn the user? eg,
Should an already reactive object warn the user if attempted to be made non-reactive? eg,
The text was updated successfully, but these errors were encountered: