Search code examples
vue.jsevent-loop

In Vue, which has a higher priority, manually changing the DOM content or updating the DOM content through virtual DOM?


I've been studying Vue's nextTick recently, I wrote myself the following demo code:

<html>
  <head>
    <script src="https://cdn.jsdelivr.net/npm/vue@2.7.14/dist/vue.js"></script>
  </head>
  <body>
    <div id="root"></div>
  </body>
  <script>
    const vue = new Vue({
      el: '#root',
      template: '<div><div id="dom">{{msg}}</div><button @click="add">add</button></div>',
      data() {
        return {
          msg: 1
        }
      },
      watch: {
        msg(value) {
          console.log('msg>>>>>', value);
        }
      },
      methods: {
        add() {
          const dom = document.getElementById('dom');
          dom.textContent = '30'
          for(let i = 0; i < 3; i ++) {
            this.msg ++
          }
          this.$nextTick(() => {
            console.log('msg.content2>>>>>>', dom.textContent)
          })
        }
      }
    })
  </script>
</html>

liveDemo: jsfiddle

the result really confused me. As i expected, after click the add button, dom.textContent = '30' change dom element's textConent to 30. and the next for loop, add msg to 4, as Vue will update page asynchronously, we cant see the newest result in the page. after this.$nextTick, Vue will use the updated msg to reRender the dom element immediately, which we know is 4 now. so the dom element's textContent should be 4 now, but it remains 30. why did this happen? where did i get wrong ?


Solution

  • I think I've figured out what's going on here:

    When dom.textContent = '30' is executed, it changes the element's text content to '30'.

    But it's not like what I thought before, which directly modifies the textContent of the dom element's Text child node to the new content. Instead, it generates a new Text child node and replaces the original one.

    Later, in the $nextTick function, Vue uses the newest msg value, which is now 4, to update the Text node's content. However, the Text node Vue is using now is the one that stored in the el property, which is the old one, not the updated dom element's Text child node. As a result, this change is useless, and both the page and the console.log statement fail to show the expected result.

    After change the demo code into the following version, I finally get what I want:

    <html>
      <head>
        <script src="https://cdn.jsdelivr.net/npm/vue@2.7.14/dist/vue.js"></script>
      </head>
      <body>
        <div id="root"></div>
      </body>
      <script>
        const vue = new Vue({
          el: '#root',
          template: '<div><div id="dom">{{msg}}</div><button @click="add">add</button></div>',
          data() {
            return {
              msg: 1
            }
          },
          watch: {
            msg(value) {
              console.log('msg>>>>>', value);
            }
          },
          methods: {
            add() {
              const dom = document.getElementById('dom');
              dom.childNodes[0].textContent = '30'; //change dom's child node's textContent, not dom's own textContent; 
              for(let i = 0; i < 3; i ++) {
                this.msg ++
              }
              this.$nextTick(() => {
                console.log('msg.content2>>>>>>', dom.textContent)
              })
            }
          }
        })
      </script>
    </html>
    

    live demo

    another demo:
    change div's textContent creates a new Text node