从源码里了解vue中的nextTick的使用

今天做了一个需求,场景是这样的:

在页面拉取一个接口,这个接口返回一些数据,这些数据是这个页面的一个浮层组件要依赖的,然后我在接口一返回数据就展示了这个浮层组件,展示的同时,上报一些数据给后台(这些数据就是父组件从接口拿的),这个时候,神奇的事情发生了,虽然我拿到数据了,但是浮层展现的时候,这些数据还未更新到组件上去。

父组件:

<template> ..... <pop ref="pop" :name="name"/> </template> <script> export default { ..... created() { .... // 请求数据,并从接口获取数据 Data.get({ url: xxxx, success: (data) => { // 问题出现在这里,我们赋值以后直接调用show方法,去展现,show方法调用的同时上报数据,而上报的数据这个时候还未更新到子组件 this.name = data.name this.$refs.pop.show() } }) } } </script>

子组件

<template> <div v-show="isShow"> ...... </div> </template> <script> export default { ..... props: ['name'], methods: { show() { this.isShow = true // 上报 Report('xxx', {name: this.name}) } } } </script>

问题分析:

原因vue官网上有解析( cn.vuejs.org/v2/guide/re… )

可能你还没有注意到,Vue 异步执行 DOM 更新。只要观察到数据变化,Vue 将开启一个队列,并缓冲在同一事件循环中发生的所有数据改变。如果同一个 watcher 被多次触发,只会被推入到队列中一次。这种在缓冲时去除重复数据对于避免不必要的计算和 DOM 操作上非常重要。然后,在下一个的事件循环“tick”中,Vue 刷新队列并执行实际 (已去重的) 工作。Vue 在内部尝试对异步队列使用原生的 Promise.then 和 MessageChannel,如果执行环境不支持,会采用 setTimeout(fn, 0) 代替。

这句话就是说,当我们在父组件设置this.name=name的时候,vue并不会直接更新到子组件中(dom的更新也一样未立即执行),而是把这些更新操作全部放入到一个队列当中,同个组件的所有这些赋值操作,都作为一个watcher的更新操作放入这个队列当中,然后等到事件循环结束的时候,一次性从这个队列当中获取所有的wathcer执行更新操作。在我们这个例子当中,就是我们在调用show的时候,实际上,我们的this.name=name并未真正执行,而是被放入队列中。vue的这种做法是基于优化而做的,毋庸置疑,不然我们如果有n多个赋值vue就执行n多个dom更新,那效率将会非常的低效和不可取的。

从源码里了解vue中的nextTick的使用


下文中的更新操作指对data的值进行更新的操作,在vue中,都会被放入队列异步执行。

解决方案:

1、 使用nextTick来延迟执行show方法(笼统得说,执行所有需要在数据真正更新后的操作

通过上面的分析我们知道,我们的所有的对vue实例的更新操作,都会先被放入一个队列当中,延迟异步执行,这些异步操作,要么是microtask,要么是macrotask(是microtask还是macroktask取决于环境,nextTick的源码中有所体现),根据事件循环机制,先入队列的先执行,所以如果我们在nextTick当中执行操作就会变成这样。

从源码里了解vue中的nextTick的使用


2、 使用setTimeout来延迟执行show方法,原理同上

所以我们的解决方法可以是:

this.name = data.name setTimeout(() => { this.$refs.pop.show() })

或者

this.name = data.name this.$nextTick(() => { this.$refs.pop.show() })

nextTick的实现原理

其实nextTick的实现原理是挺简单的,简单点说,就是实现异步,通过不同的执行环境,用不同的方式来实现,保证nextTick里面的回调函数能够异步执行。为什么要这么做呢?因为vue对dom的更新也是异步的呀。

下面贴出源码:

/** * Defer a task to execute it asynchronously. */ export const nextTick = (function () { const callbacks = [] let pending = false let timerFunc function nextTickHandler () { pending = false const copies = callbacks.slice(0) callbacks.length = 0 for (let i = 0; i < copies.length; i++) { copies[i]() } } // the nextTick behavior leverages the microtask queue, which can be accessed // via either native Promise.then or MutationObserver. // MutationObserver has wider support, however it is seriously bugged in // UIWebView in iOS >= 9.3.3 when triggered in touch event handlers. It // completely stops working after triggering a few times... so, if native // Promise is available, we will use it: /* istanbul ignore if */ if (typeof Promise !== 'undefined' && isNative(Promise)) { var p = Promise.resolve() var logError = err => { console.error(err) } timerFunc = () => { p.then(nextTickHandler).catch(logError) // in problematic UIWebViews, Promise.then doesn't completely break, but // it can get stuck in a weird state where callbacks are pushed into the // microtask queue but the queue isn't being flushed, until the browser // needs to do some other work, e.g. handle a timer. Therefore we can // "force" the microtask queue to be flushed by adding an empty timer. if (isIOS) setTimeout(noop) } } else if (!isIE && typeof MutationObserver !== 'undefined' && ( isNative(MutationObserver) || // PhantomJS and iOS 7.x MutationObserver.toString() === '[object MutationObserverConstructor]' )) { // use MutationObserver where native Promise is not available, // e.g. PhantomJS, iOS7, Android 4.4 var counter = 1 var observer = new MutationObserver(nextTickHandler) var textNode = document.createTextNode(String(counter)) observer.observe(textNode, { characterData: true }) timerFunc = () => { counter = (counter + 1) % 2 textNode.data = String(counter) } } else { // fallback to setTimeout /* istanbul ignore next */ timerFunc = () => { setTimeout(nextTickHandler, 0) } } return function queueNextTick (cb?: Function, ctx?: Object) { let _resolve callbacks.push(() => { if (cb) { try { cb.call(ctx) } catch (e) { handleError(e, ctx, 'nextTick') } } else if (_resolve) { _resolve(ctx) } }) if (!pending) { pending = true timerFunc() } if (!cb && typeof Promise !== 'undefined') { return new Promise((resolve, reject) => { _resolve = resolve }) } } })()

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:http://www.heiqu.com/eb23eb5a0c155a2b70064907f2b14eb3.html