Vue中的nextTick的一个问题


<template>
  <div>
    <div ref="list" v-for="(num, index) in abc" :key="index">{{ num }}</div>
    <button @click="clickFN">点我</button>
  </div>
</template>

<script>
export default {
  name: 'App',

  data() {
    return { abc: [+new Date()] }
  },

  methods: {
    clickFN() {
      this.abc.push(+new Date())
      const list = this.$refs.list
      console.log(list, list.length)
    }
  }
}
</script>

<style lang="css" scoped></style>


js的同步异步我知道,vue的dom是异步操作知道,
我想点击按钮的时候,abc数组push最新的时间戳,然后在控制台打印,this.$refs.list.length,但是打印出来每次的length都少一个数

img


我也会用nextTick来解决这个问题。
我的疑问是:nexttick根据vue官方的解释是,"在下一次 DOM 更新结束后执行其指定的回调",但是页面上已经有新的标签元素了啊,标签元素不也是dom的一部分吗?为什么不使用nexttick的情况下,拿到的length和页面上的元素总数不同呢?我卡在这里了。我自己认为,点击了按钮之后,abc已经push了新数据了,而且新数据产生的标签元素,已经在页面上呈现出来了不是吗?那么为什么拿到的length确实缺少的呢?

数据渲染是数据渲染,dom渲染是dom渲染

简单的说就像 created 与 mounted的区别一样,一个是数据挂载,一个是视图渲染

既然有两个生命周期 就很明显的知道 数据挂载的时机与视图渲染的时机是不同的,这样相当于是最简单的理解

包括一系列的监听,很简单的道理 script 与 template 两者其实你可以认为是没有任何关系的,可以推荐你去理解一下vue的观察者模式 也叫做发布订阅者模式

当一个数据变更时,并不是会立刻将视图进行更新

img

看下图层的 逻辑关系

this.xxx变更时 通知object.defineProperty 通知 Dep、Wather等进行中转,这一步只是实现数据的变更,所以说你可以抓到最新的数据,也就是说你在 this.abc.push(+new Date()) 下方抓到的是最新的数据,当然这里面并不是做了简单的最新数据处理,更下一步是要根据diff比对,实现数据图形的最小量更新,这个时候你直接去用ref抓数据,其实只是抓到的上一层视图的数据,因为并没有还没有对视图进行更新

那么在视图更新完成之后 vue对外暴露出 $nextTick钩子,你可以在这个钩子中抓到最新的dom结构 所以逻辑大概就是这样一个逻辑

出现你这个问题的原因是,vue对dom更新做了优化,不会在数据更新后的第一时间立即更新dom。
而是需要收集更新信息,然后现在虚拟dom中更新,最后才会映射到真实dom。这期间还有很多别的东西!
而你的js代码是同步的一次直接执行完的,也就是说当你通过ref打印的时候,真实dom上面还没更新到。
nextTick这个事件,参数是一个回调,这个回调会在下一次dom更新完成后。
而你上面push了一个数据,也就是说,这个回调会在你push的那个dom更新之后被执行。

因为拿到的数你push后的值,初始化时,循环已经结束了,要是使用 $nextTick 拿到的是更新DOM的时候的值 不记录历史数据

this.$nextTick(()=>{
  const list = this.$refs.list
  console.log(list, list.length)
})