2
votes

When I have a Vue component in a .vue file with a data member isLoading: false, and a template:

<div v-show="isLoading" id="hey" ref="hey">Loading...</div>
<button @click="loadIt()">Load it</button>

And a method:

loadIt() {
  this.isLoading = true
  this.$nextTick(() => {
    console.log(this.$refs.hey)                  // virtual DOM
    console.log(document.getElementById('hey'))  // actual DOM
    // ...other work here
  })
}

I thought that the nextTick function would allow both the virtual and real DOM to update, and thus the two console.log lines would output the same results. However, they do not: it seems that the real DOM isn't being updated right away, and thus the second log results in a element with display: none; whereas the first log doesn't--I get this on the console:

<div id="hey" data-v-964d645e="" style="">
<div id="hey" data-v-964d645e="" style="display: none;">

(By the way, even if I use setTimeout instead of this.$nextTick, I get the very same results from console.log. I also tried using the updated hook, but the same symptoms happen there. If I code any variation in a .js file, the problem goes away, but it persists when in a .vue file.)

Is there some kind of optimization or further asynchrony in how Vue updates the actual DOM from the virtual DOM? How do I get the actual DOM to update right away?

3
Note that console can update the display after the fact (usually see an info icon). To be precise, you should save the state of this.$refs.hey.getAttribute("style") for an accurate snapshot. Does not negate your issue, though.Richard Matsen
I can't reproduce on Codesandbox. Are you able to alter it to reproduce?Richard Matsen
this.$refs.hey is not a virtual DOM element; it's an actual DOM elementStephen Thomas
I think all of you are right. Thanks for helping me find the problem. My follow-up is here: stackoverflow.com/questions/53707415Patrick Szalapski

3 Answers

2
votes

This works pretty much as expected, with messages matching both before and after DOM update.

I think your understanding that the refs call returns a virtual node rather than an actual DOM element is incorrect.

new Vue({
  el: '#app',
  data: {
    isLoading: false
  },
  methods: {
    loadIt() {
      this.isLoading = true;
      this.tellMeAboutIt('before');
      this.$nextTick(() => this.tellMeAboutIt('after'));
    },
    tellMeAboutIt(when) {
        console.log(`Virtual ${when}:`, this.$refs.hey) // virtual DOM
        console.log(`Actual ${when}:`, document.getElementById('hey')) // actual DOM
    }
  }
});
<script src="https://unpkg.com/vue@latest/dist/vue.js"></script>
<div id="app">
  <div v-show="isLoading" id="hey" ref="hey">Loading...</div>
  <button @click="loadIt()">Load it</button>
</div>
1
votes

Please review Vue lifecycle in the documentation. Of note is that there is a disconnect between these two events. Also note that nextTick() waits for the next DOM update cycle, not necessarily the virtual DOM.

This is typically addressed by using the updated lifecycle hook, which executes code after the virtual DOM has already been updated. If you need to execute some code with the guarantee that the virtual DOM has already been updated, you will want to do it there.

You may also be interested in reactivity in depth. This should act as a good complement to the lifecycle diagram.

0
votes

Turns out that problems with either the eslint-loader cache or the babel-loader cache caused this weirdness. I could only fix it by deleting all of node_modules\.cache. Too bad I don't know why it happened in the first place.