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

non-reactive state responds reactively #2281

Closed
smilingkite opened this issue Sep 30, 2020 · 16 comments
Closed

non-reactive state responds reactively #2281

smilingkite opened this issue Sep 30, 2020 · 16 comments

Comments

@smilingkite
Copy link

smilingkite commented Sep 30, 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.

@RobbinBaauw
Copy link
Contributor

RobbinBaauw commented Sep 30, 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,
    };
  },

@smilingkite
Copy link
Author

smilingkite commented Sep 30, 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.

@KaelWD
Copy link
Contributor

KaelWD commented Sep 30, 2020

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

@smilingkite
Copy link
Author

smilingkite commented Sep 30, 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.

@RobbinBaauw
Copy link
Contributor

RobbinBaauw commented Sep 30, 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.

@LinusBorg
Copy link
Member

LinusBorg commented Sep 30, 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.

@smilingkite
Copy link
Author

smilingkite commented Sep 30, 2020

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

@LinusBorg
Copy link
Member

LinusBorg commented Sep 30, 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.

@smilingkite
Copy link
Author

smilingkite commented Sep 30, 2020

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

@smilingkite
Copy link
Author

smilingkite commented Sep 30, 2020

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

@LinusBorg
Copy link
Member

LinusBorg commented Sep 30, 2020

created() {
  this.nonReactiveCount = 0
}

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

@smilingkite
Copy link
Author

smilingkite commented Oct 3, 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/

@LinusBorg
Copy link
Member

LinusBorg commented Oct 3, 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

@RobbinBaauw
Copy link
Contributor

RobbinBaauw commented Oct 3, 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)

@smilingkite
Copy link
Author

smilingkite commented Oct 5, 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.

@LinusBorg
Copy link
Member

LinusBorg commented Oct 5, 2020

You got it right 🙂

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

4 participants