浅谈Vue.nextTick 的实现方法

当数据发生变化后,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日

相关文章

  • 详解windows下vue-cli及webpack 构建网站(四) 路由vue-router的使用

    接下来我将详细讲解“详解windows下vue-cli及webpack 构建网站(四) 路由vue-router的使用”的完整攻略。 标题和前言 标题 “详解windows下vue-cli及webpack 构建网站(四) 路由vue-router的使用” 前言 当我们的网站变得越来越复杂时,我们需要将页面拆分为多个模块和页面,通过路由跳转实现,在这篇文章中,…

    Vue 2023年5月28日
    00
  • Vue组件间的通信pubsub-js实现步骤解析

    下面我将详细讲解“Vue组件间的通信pubsub-js实现步骤解析”。 什么是pubsub-js? pubsub-js是一个基于发布/订阅模式的Javascript库,提供了一种解耦的方式,让我们的代码更加灵活和易于维护。在Vue组件间的通信中,我们可以使用pubsub-js来实现跨组件的数据传递。 pubsub-js的安装 我们可以使用npm或yarn在项…

    Vue 2023年5月28日
    00
  • vue引入iconfont图标库的优雅实战记录

    下面为你介绍如何优雅地在Vue中引入Iconfont图标库。 1. 注册Iconfont账号并创建项目 首先,在Iconfont官网上注册一个账号,并创建一个项目。 2. 选择图标并添加至项目 在项目中选择需要使用的图标,并将其添加至项目。 3. 生成Font-class代码 选择添加至项目的图标后,在页面右上角点击“生成代码”按钮,选择“Font-clas…

    Vue 2023年5月27日
    00
  • vue3使用reactive包裹数组正确赋值问题

    当我们在Vue3中使用reactive包装一个对象时,如果该对象中存在数组,我们需要特别注意对数组进行正确的赋值。在Vue3中对数组的正确赋值方式相对于Vue2中有了一定的变化。 下面是一个使用Vue3中的reactive包装的对象的示例: import { reactive } from ‘vue’ const state = reactive({ nam…

    Vue 2023年5月28日
    00
  • Vue3响应式对象是如何实现的(1)

    当我们使用Vue3来开发应用程序时,我们可能会频繁地使用响应式对象。那么,Vue3响应式对象是如何实现的呢? 在Vue3中,响应式对象是通过使用Proxy对象来实现的。Proxy是ES6的一个新特性,可以用来拦截JavaScript对象的操作。通过使用Proxy对象,我们可以实现Vue3的响应式对象功能。 下面,让我们通过两个示例来详细讲解Vue3响应式对象…

    Vue 2023年5月27日
    00
  • 详解微信小程序框架wepy踩坑记录(与vue对比)

    接下来我将详细讲解一下“详解微信小程序框架wepy踩坑记录(与vue对比)”这篇文章的完整攻略。 标题 首先,文章的标题应该清晰、明确、准确地概括文章的主题内容,能够体现文章的重点和亮点。 引言 在引言中,应该简要介绍wepy框架和vue框架,并指出其优缺点。 体 在主要内容的部分,应该详细讲解wepy框架的使用、踩坑记录和与vue框架的对比。需要突出wep…

    Vue 2023年5月27日
    00
  • Nuxt.js实战和配置详解

    Nuxt.js是一个基于Vue.js的服务端渲染框架,它可以帮助开发者快速构建出高效可靠的Web应用程序。本文将详细讲解Nuxt.js实战和配置详解,包括安装、初始化项目、路由配置、视图渲染等内容。 安装 首先,在全局安装vue-cli脚手架: npm install -g vue-cli 接着,初始化一个项目: vue init nuxt-communit…

    Vue 2023年5月28日
    00
  • vue3使用vue-router的完整步骤记录

    下面就是“Vue3使用Vue Router的完整步骤记录”的攻略。 使用Vue Router包 要使用Vue Router,首先需要安装vue-router包。可以使用npm安装,命令如下所示: npm install vue-router@next 创建路由实例 Vue Router的创建需要在Vue实例之前,因为Vue Router的实例也要在Vue实例…

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