Vue的DOM异步更新

2,548 阅读3分钟

数据驱动

数据驱动是Vue的核心思想,在Vue中我们只需要通过操作数据来操作DOM(视图),什么意思呢?例如我们需要更改一个文本。

在原生JS中

<p id="title">你好<p>
<script>
    let p1 = document.getElementById('title')  
    p1.innerText = 'hello'
</script>

在Vue中

<p id="app">{{text}}</p>
<script>
    let vm = new App({
        el: '#app',
        data:{
            text:'你好'
        }
    })
    vm.text = 'hello'
</script>

在原生JS中我们需要先获取到DOM,然后对DOM进行操作从而改变文本,但是在Vue中,我们只需要对数据进行修改就能更改对应的DOM,我们并不需要直接操作DOM就能够实现对视图的修改。这就是Vue最核心的思想,视图(DOM)和数据是绑定的,通过操作数据就能够操作DOM。

虚拟DOM

这是因为在Vue中,DOM和数据之间有映射关系,而这映射就是通过虚拟DOM来实现的,可以将虚拟DOM理解成一个真实DOM的描述对象,即虚拟DOM就是个对象,用于表示一个真实DOM结构的数据。 Vue会将模板编译然后创建虚拟DOM,这样我们通过操作数据(虚拟DOM)就能够完成对真实DOM的修改。那虚拟DOM只是个JS数据,需要通过Vue把这个虚拟DOM渲染成一个真实的DOM。

下图就是Vue创建的虚拟DOM,也就是用一个对象来表示一个DOM结构 image.png

render函数

render函数是用于将数据生成对应的虚拟DOM的。 当我们修改数据后,Vue会重新生成虚拟DOM,然后完成DOM的更新,我们可以通过render函数去验证这点。 render函数用于创建虚拟DOM的,当我们修改数据后这个render函数会重新执行一次,也就是会再次生产一个虚拟DOM。

<div id="app" ref="root"></div>
<script>
       var app = new Vue({
            el: '#app',
            data: {
                text:'hello'
            },
            render:function(h){
                var vdom = h('div',{domProps:{innerHTML:this.text}}) 
                console.log('渲染函数执行了')
                return vdom
            }
        })
        app.text="你好"  
</script>

看浏览器控制台:

image.png

说明当我们修改数据后,Vue会重新生产虚拟DOM,然后根据这个虚拟DOM对真实DOM进行更新。

DOM异步更新

现在已经知道了,在Vue中,我们是通过操作数据(虚拟DOM)从而实现对DOM的操作的。这就意味着我们如果更改了数据,对应的DOM应该就会跟着改变。但需要注意的是:DOM的更新是异步的,这就意味着,并不是你修改了数据,其对应的真实的DOM就会立马完成更新。

Vue的官方文档是这样说的:

可能你还没有注意到,Vue 在更新 DOM 时是异步执行的。只要侦听到数据变化,Vue 将开启一个队列,并缓冲在同一事件循环中发生的所有数据变更。例如,当你设置 vm.someData = 'new value',该组件不会立即重新渲染。

可以通过代码验证:

<div id="app{{ text }}</div>


<script>
    var vm = new Vue({
        el: '#',
        data: {
            text: '我是旧值'
         }
         })
    vm.text = '我是新值'              // 更改了数据
    console.log(vm.$el.textContent)  // 打印值: 我是旧值
</script>

虽然数据已经修改了,但修改后立刻访问DOM,仍是旧值。也代表着我们获取的不是最新的DOM,如何才能在DOM完成更新后操作DOM呢?这就需要使用到Vue提供的API,即Vue.nextTick。

Vue.nextTick

这个是Vue提供的API,它可以在DOM更新完毕之后执行一个回调。也就是说这个API能够检测DOM是否完成了更新,然后在DOM更新后执行回调。

<div id="app">{{ text }}</div>


<script>
    var vm = new Vue({
        el: '#app,
        data: {
            text: '我是旧值'
         }
         })
    vm.text = '我是新值'              // 更改了数据
    Vue.nextTick(function(){
        console.log(vm.$el.textContent) //打印值: 我是新值
    })
</script>

所以,官网也说过,mounted不会保证所有的子组件也都一起被挂载。如果你希望等到整个视图都渲染完毕,可以在 mounted 内部使用 vm.$nextTick。 至于,nextTick的原理,它到底是如何检测DOM完成更新的,下次文章再分析。