Subscribe on changes!

non-reactive state responds reactively

avatar
Sep 30th 2020

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

  1. that only the reactive state gets updated
  2. (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.

avatar
Sep 30th 2020

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,
    };
  },
avatar
Sep 30th 2020

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.

avatar
Sep 30th 2020

It isn't even reactive, you're just triggering a rerender by updating count

avatar
Sep 30th 2020

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

avatar
Sep 30th 2020

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.

avatar
Sep 30th 2020

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.

avatar
Sep 30th 2020

But then why is nonReactiveCount changing? You can create some very funky stuff this way.

avatar
Sep 30th 2020

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.

avatar
Sep 30th 2020

Maybe, but in vue 2 you'd never find out, because it's difficult to do non-reactive stuff.

avatar
Sep 30th 2020

This is very educational. Thanks everybody. But not a bug, I guess.

avatar
Sep 30th 2020
created() {
  this.nonReactiveCount = 0
}

not really difficult, I saw countless instances of things like this

avatar
Oct 3rd 2020

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/

avatar
Oct 3rd 2020

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

avatar
Oct 3rd 2020

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

avatar
Oct 5th 2020

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.

avatar
Oct 5th 2020

You got it right 🙂