non-reactive state responds reactively
Version
3.0.0
Reproduction link
https://codesandbox.io/s/interesting-river-x387u?file=/src/App.vue
Steps to reproduce
Click on button > both the reactive and the non-reactive state-items get updated.
setup() {
const count = ref(0);
let nonReactiveCount = 0;
return {
count,
nonReactiveCount,
};
},
methods: {
increment() {
this.count = this.count + 1 ;
this.nonReactiveCount = this.nonReactiveCount + 1;
},
},
What is expected?
I expect
- that only the reactive state gets updated
- (ultimately) a console warning that I'm trying to update unreactive state.
What is actually happening?
state-item which was designed to not be reactive, is updating.
Context: windows, chrome.
This is because you are mixing the VCA and Options API, by using methods
. This uses the internal proxy, which makes this non-reactive state reactive as well.
It will work as expected if you only use VCA:
setup() {
const count = ref(0);
let nonReactiveCount = 0;
function increment() {
count.value++;
nonReactiveCount++;
}
return {
count,
increment,
nonReactiveCount,
};
},
Is this expected to be fixed? I was expecting to be able to create non-reactive state this way (you know, save a bit of computing power etc). But if it's reactive (or becomes reactive?) when using methods, I guess that's off the table.
So best practices is to not mix the two?
This is going to trip a lot of people up. That is: a lot of people migrating from vue 2.* are going to be mixing the two apis. And this is clearly the easiest way to create something reactive through the setup method, without using ref or reactive. I can already envision a lot of messy code using this backdoor.
@KaelWD Yes, it was. The code-example just wasn't showing correctly (it is in the code-sandbox). I edited it to show my intent.
Actually, I think the methods
way updates the setupState
because methods
are bound to the public instance proxy. Hence assignments are actually proxied and the original setupState
object is updated. Note that this doesn't trigger the rerender, updating the ref
takes care of that, this only explains why the update is visible.
When only using VCA, the original setupState
object is not updated (as the number is copied once to this object, no reference) and hence a rerender won't show the updated value.
In both cases they are plain numbers in an object, but when using methods
the update is visible because the update goes through the proxy.
You're on the wrong track. comment out this.count++
in the codesandbox and you will see that nothing is reactive anymore.
So no, nothing is being "made" reactive by using methods.
Because you changed it?
You did both a reactive change (this.count++
) and a nonreactive change (this. nonReactiveCount++
) in one method call.
The reactive change caused a re-render of the component, and during that re-render, the new values for both properties are being used.
That works just like Vue 2.
created() {
this.nonReactiveCount = 0
}
not really difficult, I saw countless instances of things like this
For anybody else running into this, apparently this is not a bug, but a legacy feature, but there IS a difference between how the options api and the composition api handle non-reactive properties.
I worked out the differences here: https://www.hesselinkwebdesign.nl/2020/reactivity-and-non-reactivity-introduction-for-vue-2-and-vue-3/
The difference you describe there in the last example is rooters in Javascript itself. Changing the let variable will not update the property if the same name that you returned from setup.
Not because of Vue or reactivity. It's because in Javascript,, primitive values can't be referenced, so when you returned it from setup You created a copy of the value 0
but then continue to change the original value, not the copy.
Again, this is basic vanilla Javascript behaviour, Vue has zero to do with it.
Or to put it another way: that Javascript behaviour is in large part the reason we have ref()
in Vue 3
^ I was just going to write that. Try
let nonReactiveCount = {
x: 0
};
const incrementNonReactive = () => {
nonReactiveCount.x++;
};
and upon rerender because of the ref change it will actually update in the view (this is what I was trying to explain before but it wasn't very clear)
Ok, let me see if I've got this right.
In the setup method I've set a variable 'nonReactiveCount' to 0. Its value gets exported to the vue-instance as the value of the exported 'nonReactiveCount' key. It's not reactive.
I can increment this.nonReactiveCount through the methods, because it's available on the 'this'. I can see that reflected in the template as soon as a reactive change updates the virtual dom. this.nonReactiveCount no longer has anything to do with the variable 'nonReactiveCount' in the setup method (and to correct my blogpost I guess I'll be showing that).
Through the incrementNonReactive function in the setup method, I can increment the variable 'nonReactiveCount' within the setup method. However, other than logging it, nothing else happens with that new value, because the variable isn't available outside the setup method.
It only seems like it's exported because of various shorthands used in the code: return { nonReactiveCount } means: return { nonReactiveCount: value-of-nonReactiveCount }
nonReactiveCount does not (for instance) get re-exported when it changes. The export happens on setup (hence the name setup-method) and the value it has at that point is the value assigned to this.nonReactiveCount. this.nonReactiveCount and nonReactiveCount aren't connected after that.