浅谈Vue.nextTick 的实现方法

yizhihongxing

当数据发生变化后,Vue 不会立即更新 DOM,而是加入一个更新队列,并在下一次事件循环中更新视图。而 Vue.nextTick 可以在下次 DOM 更新循环结束之后执行回调。这使得我们可以在 DOM 变化后立即操作更新后的 DOM,例如获取更新后的元素宽高等。

一、Vue.nextTick 的调用方式

Vue 提供了两种调用方式:

1.1 全局调用方式

Vue.nextTick(() => {
  // DOM 更新完毕后的回调
})

1.2 Vue 实例上的调用方式

new Vue({
  // ...
  methods: {
    doSomething: function () {
      // ...
      this.$nextTick(function () {
        // DOM 更新完毕后的回调
      })
    }
  }
})

二、Vue.nextTick 的实现方法

Vue 通过将回调函数处理成微任务或宏任务来实现 Vue.nextTick,具体的实现方法如下:

2.1 将回调函数封装成一个 Watcher

使用 Vue.$watch 方法来实现将回调函数封装成一个 Watcher 对象:

Watcher.prototype.run = function () {
  this.getAndInvoke(this.cb) // 执行回调函数
}

function Watcher () {
  // 将回调函数绑定到该 Watcher 上
  this.cb = cb
  this.vm = vm
  this.cb()
}

2.2 将 Watcher 放入异步队列中

Vue 通过异步队列来统一管理所有需要监听的 Watcher。在同一个事件循环中,Vue 将多次更新的 Watcher 排队执行,从而减少重复更新。

const queue = []
let has = {}
let waiting = false

function queueWatcher(watcher) {
  const id = watcher.id

  // 如果队列中不存在 ID 匹配的 Watcher
  // 则加入队列并标记已存在
  if (!has[id]) {
    has[id] = true
    queue.push(watcher)

    // 判断异步队列是否处于等待状态
    // 如果没有在等待,则调用 nextTickFlush 执行异步任务
    if (!waiting) {
      waiting = true
      nextTick(nextTickFlush)
    }
  }
}

function nextTickFlush() {
  waiting = false
  const copiedQueue = queue.slice(0)
  queue.length = 0
  has = {}

  // 遍历队列中的 Watcher,逐一执行回调函数
  copiedQueue.forEach(watcher => {
    watcher.run()
  })
}

2.3 将 Watcher 加入异步队列中的方式:微任务 / 宏任务

在异步执行回调函数的过程中,Vue.nextTick 的实现方法可能采用微任务或宏任务的方式,Vue 会依次尝试以下方式,根据支持情况来决定最终的方式:

// 尝试使用 Promise 微任务(常规方法)
if (typeof Promise !== 'undefined') {
  const p = Promise.resolve()
  timerFunc = () => {
    p.then(nextTickFlush)
  }
}
// 尝试使用 MutationObserver 微任务
else if (typeof MutationObserver !== 'undefined') {
  let counter = 1
  const observer = new MutationObserver(nextTickFlush)
  const textNode = document.createTextNode(String(counter))
  observer.observe(textNode, { characterData: true })
  timerFunc = () => {
    counter = (counter + 1) % 2
    textNode.data = String(counter)
  }
}
// 尝试使用 setImmediate 宏任务
else if (typeof setImmediate !== 'undefined') {
  timerFunc = () => {
    setImmediate(nextTickFlush)
  }
}
// 尝试使用 setTimeout 宏任务
else {
  timerFunc = () => {
    setTimeout(nextTickFlush, 0)
  }
}

三、示例说明

3.1 示例一:组件中使用 Vue.nextTick

在下面的示例中,一个组件内部的 data 属性改变后,使用 Vue.nextTick 来获取更新后的 DOM 节点信息,并动态设置一个元素的样式。

<template>
  <div>
    <p>当前计数:{{ count }}</p>
    <button @click="increment">增加计数</button>
    <div ref="box" :style="boxStyle">待更新元素</div>
  </div>
</template>
<script>
export default {
  data() {
    return {
      count: 0
    }
  },
  methods: {
    increment() {
      this.count++
      this.$nextTick(() => {
        const box = this.$refs.box
        const { width, height } = box.getBoundingClientRect()
        const size = Math.min(width, height)
        box.style.width = size + 'px'
        box.style.height = size + 'px'
      })
    }
  },
  computed: {
    boxStyle() {
      return {
        border: '1px solid #ddd',
        textAlign: 'center',
        transition: 'all .5s',
        cursor: 'pointer'
      }
    }
  }
}
</script>

3.2 示例二:在方法中使用 Vue.nextTick

在下面的示例中,一个方法内部先改变了某个元素的样式,再使用 Vue.nextTick 来获取该元素更新后的宽度和高度。

<template>
  <div>
    <p>当前宽度:{{ width }},高度:{{ height }}</p>
    <button @click="updateBox">更新元素</button>
    <div ref="box" class="box"></div>
  </div>
</template>
<script>
export default {
  data() {
    return {
      width: 0,
      height: 0
    }
  },
  methods: {
    updateBox() {
      const box = this.$refs.box
      box.style.width = '200px'
      box.style.height = '100px'
      this.$nextTick(() => {
        const { width, height } = box.getBoundingClientRect()
        this.width = width
        this.height = height
      })
    }
  }
}
</script>

<style>
.box {
  background-color: #eee;
  width: 100px;
  height: 50px;
}
</style>

本站文章如无特殊说明,均为本站原创,如若转载,请注明出处:浅谈Vue.nextTick 的实现方法 - Python技术站

(0)
上一篇 2023年5月29日
下一篇 2023年5月29日

相关文章

  • vue动态绑定ref(使用变量)以及获取方式

    在Vue中,我们经常需要对DOM元素进行操作,比如获取dom元素的数值或进行样式改变,这时我们就需要获取到DOM元素的引用。Vue提供了ref属性,可以用来获取dom元素的引用。本文将详细介绍如何使用动态绑定ref以及获取dom元素的方法。 动态绑定ref Vue的ref属性可以绑定到元素身上,当元素渲染后,这个元素会挂载到vm.$refs或this.$re…

    Vue 2023年5月27日
    00
  • vue2单元测试环境搭建

    Vue2单元测试环境搭建 单元测试是为了保证代码质量而必要的步骤。Vue2的单元测试环境搭建涉及的技术包括mocha、chai、sinon等。本文将详细讲述Vue2单元测试环境搭建的步骤和示例。 步骤 安装依赖 首先需要安装mocha、chai、sinon和vue-test-utils等依赖。 npm install –save-dev mocha cha…

    Vue 2023年5月28日
    00
  • vue项目常用组件和框架结构介绍

    下面我将为你详细讲解”vue项目常用组件和框架结构介绍”的攻略。 1. 常用组件 在Vue项目开发中,常用的组件有: (1) vue-router 路由是Vue应用中最重要的一部分,它可以帮助我们实现单页应用页面之间的跳转。vue-router是Vue官方提供的路由器,它能轻松地与Vue应用进行整合,可以实现前端路由的效果。 (2) vuex Vuex是一种…

    Vue 2023年5月28日
    00
  • vue实现几秒后跳转新页面代码

    当我们需要在vue中实现几秒后跳转到新页面时,可以通过使用定时器(setTimeout)实现。 步骤如下: 1.在需要实现跳转的组件或页面中导入Vue,使用Vue的原型链上的$router对象,使用其中的push()方法实现路由跳转。代码如下: import Vue from ‘vue’ export default { data () { return {…

    Vue 2023年5月29日
    00
  • vue 实现列表跳转至详情且能添加至购物车功能

    下面是“vue 实现列表跳转至详情且能添加至购物车功能”的攻略。该攻略的主要步骤如下: 构建商品列表页 构建商品详情页 实现跳转及传参功能 实现购物车功能 下面将详细介绍这些步骤。 构建商品列表页 首先需要构建一个商品列表页面,用于展示商品列表及其相关信息。可以使用v-for指令循环遍历商品数组,并通过router-link标签实现跳转到商品详情页。示例代码…

    Vue 2023年5月27日
    00
  • vue中template的三种写法示例

    当我们在Vue中编写组件的template时,有三种主要的写法:模板字符串、Vue模板、单文件组件。下面我们将分别进行说明。 模板字符串 模板字符串是Vue中最基本的template写法,它允许我们在JS中通过字符串的形式定义Vue模板。下面是一个使用模板字符串的基本例子: <template id="my-template"&gt…

    Vue 2023年5月27日
    00
  • vue 输入电话号码自动按3-4-4分割功能的实现代码

    实现输入电话号码自动按照 3-4-4 的格式分割,可以通过 Vue 自定义指令实现。以下是具体步骤: 1. 创建自定义指令 在 Vue 中创建自定义指令可以通过 Vue.directive 方法实现。该方法有两个参数,第一个参数是指令名称,第二个参数是指令回调函数。 Vue.directive(‘phone’, { bind: function(el, bi…

    Vue 2023年5月27日
    00
  • vue项目中引入vue-datepicker插件的详解

    引入 vue-datepicker 插件,主要需要以下几个步骤: 1. 安装 vue-datepicker 插件 使用 npm 或 yarn 安装该插件: npm install vue-datepicker –save # 或 yarn add vue-datepicker 2. 在 main.js 中注册插件 在 main.js 文件中添加如下代码: …

    Vue 2023年5月29日
    00
合作推广
合作推广
分享本页
返回顶部