preface
In a project, it is often necessary to display data at the view level immediately. Sometimes, because of asynchronous data transfer, the page will not be displayed immediately on the page. In this case, you need to use the nextTick method provided by Vue. The main reason is that Vue's data view is asynchronous and new. The official explanation is:
The responsive implementation of Vue is not the DOM changes immediately after the data changes, but the DOM updates according to certain policies.
The event cycle mentioned in this article is also a frequently asked point in the front-end interview. This article will not be expanded in detail. Interested students can refer to this article Understand the Event Loop once (thoroughly solve such interview problems)
List of step pits
- Template case data is displayed on the view
- Asynchronous data transfer between sibling components
- $nextTick source code implementation analysis
Case of stepping on a pit
Template case data is displayed on the view
Click Reset on the [bug description] page to render the template view to a view under fixed data
[bug analysis] it needs to be displayed on the page immediately after clicking. This is a typical scenario that nextTick needs to apply
[solution]
There is also a pit where array type listening is based on one address. Therefore, if you need Vue's Watcher to be able to monitor, you need to follow the methods of array listening. If you want to create a new pit here, the address will change every time, so you can monitor it
async resetTemplate() { this.template = []; await this.$nextTick(function() { this.template = [ { week: '1', starttime: '00:00:00', endtime: '00:00:00' }, { week: '2', starttime: '00:00:00', endtime: '00:00:00' }, { week: '3', starttime: '00:00:00', endtime: '00:00:00' }, { week: '4', starttime: '00:00:00', endtime: '00:00:00' }, { week: '5', starttime: '00:00:00', endtime: '00:00:00' }, { week: '6', starttime: '00:00:00', endtime: '00:00:00' }, { week: '7', starttime: '00:00:00', endtime: '00:00:00' } ]; }); }
Asynchronous data transfer between sibling components
The input field in the pop-up window of [bug description] page modification needs to be copied into the corresponding field. The data will not be modified directly after passing in the data through Props
[bug analysis] in this scenario, the data is sent to the parent component through the sub component exit. The parent component obtains the data and then passes it to the pop-up window through props. The data obtained in the v-model is asynchronous
[solution]
This is a relatively uncommon way to use nextTick to process v-model asynchronous data transfer (ps: about the introduction of EMT / on's publish and subscribe, interested students can take a look at this article [vue publish subscriber mode nextTick to process v-model asynchronous data transfer method (ps: About the introduction of the publish / subscribe mode of emit/on, interested students can take a look at this article [vue publish subscriber mode nextTick to handle the method of v-model asynchronous data transfer (ps: about the introduction of the publish / subscribe mode of emit/on, interested students can take a look at this article [vue publish subscriber mode emit, $on]( https://blog.csdn.net/qq_ 42778001 / article / details / 96692000), which uses the method that the data of the parent component is delayed to the next tick to pass to the child component, and the child component renders on the corresponding page in time. Besides this method, there are other methods. For details, please refer to this article Explain the problem of vue parent component passing props asynchronous data to child component
edit(data) { this.isManu = true; let [content,pos] = data; this.manuPos = pos; this.form = content; this.$nextTick(function(){ this.$refs.deviceEdit.form.deviceid = content.deviceId; this.$refs.deviceEdit.form.devicename = content.deviceName; this.$refs.deviceEdit.form.devicebrand = content.deviceBrand; this.$refs.deviceEdit.form.devicegroup = content.deviceGroup; this.$refs.deviceEdit.form.mediatrans = content.mediaTrans; this.$refs.deviceEdit.form.cloudstorage = content.cloudStorage; this.$refs.deviceEdit.form.longitude = content.longitude; this.$refs.deviceEdit.form.latitude = content.latitude; this.$refs.deviceEdit.form.altitude = content.altitude; }) },
$nextTick source code implementation analysis
Before 2.5:
/** * 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 }) } } })()
Version after 2.5
/* @flow */ /* globals MutationObserver */ import { noop } from 'shared/util' import { handleError } from './error' import { isIE, isIOS, isNative } from './env' export let isUsingMicroTask = false const callbacks = [] let pending = false function flushCallbacks () { pending = false const copies = callbacks.slice(0) callbacks.length = 0 for (let i = 0; i < copies.length; i++) { copies[i]() } } // Here we have async deferring wrappers using microtasks. // In 2.5 we used (macro) tasks (in combination with microtasks). // However, it has subtle problems when state is changed right before repaint // (e.g. #6813, out-in transitions). // Also, using (macro) tasks in event handler would cause some weird behaviors // that cannot be circumvented (e.g. #7109, #7153, #7546, #7834, #8109). // So we now use microtasks everywhere, again. // A major drawback of this tradeoff is that there are some scenarios // where microtasks have too high a priority and fire in between supposedly // sequential events (e.g. #4521, #6690, which have workarounds) // or even between bubbling of the same event (#6566). let timerFunc // 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 next, $flow-disable-line */ if (typeof Promise !== 'undefined' && isNative(Promise)) { const p = Promise.resolve() timerFunc = () => { p.then(flushCallbacks) // 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) } isUsingMicroTask = true } 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 // (#6466 MutationObserver is unreliable in IE11) let counter = 1 const observer = new MutationObserver(flushCallbacks) const textNode = document.createTextNode(String(counter)) observer.observe(textNode, { characterData: true }) timerFunc = () => { counter = (counter + 1) % 2 textNode.data = String(counter) } isUsingMicroTask = true } else if (typeof setImmediate !== 'undefined' && isNative(setImmediate)) { // Fallback to setImmediate. // Technically it leverages the (macro) task queue, // but it is still a better choice than setTimeout. timerFunc = () => { setImmediate(flushCallbacks) } } else { // Fallback to setTimeout. timerFunc = () => { setTimeout(flushCallbacks, 0) } } export function nextTick (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() } // $flow-disable-line if (!cb && typeof Promise !== 'undefined') { return new Promise(resolve => { _resolve = resolve }) } }
Different versions of timeFunc are mainly due to the different priority of asynchronous functions. There are some differences after 2.5, but the main problem is whether to expose the micro task function and macro task function (ps: the version after 2.5 above is 2.6.11)
Version before 2.5: promise = > mutionobserver = > setTimeout
After 2.5: setimmediate = > messagechannel = > promise = > setTimeout
summary
The asynchronous execution mechanism of js is the knowledge that front-end students must master, among which nextTick is a very typical representative. There are also nextTick related methods in node, and the implementation of related methods is often asked in interview. It is very useful for front-end development to deeply understand the basic methods and features of js, and almost all interview questions have the same problems To show knowledge and lay a good foundation is always a solid foundation for engineers to rise!
let callbacks = [] let pending = false function nextTick (cb) { callbacks.push(cb) if (!pending) { pending = true setTimeout(flushCallback, 0) } } function flushCallback () { pending = false let copies = callbacks.slice() callbacks.length = 0 copies.forEach(copy => { copy() }) }
reference resources
- Vue.nextTick Principle and application of
- A simple understanding of nextTick in Vue
- nextTick source code analysis
- Vue nextTick mechanism
- nextTick of Vue source code analysis
- On nextTick of Node
- nextTick and setTimeout of Nodejs
- Vue.js Use of this.$nextTick() in
- vue publishes subscriber mode emit, emit, emit, on
- Explain the problem of vue parent component passing props asynchronous data to child component
- Understand the Event Loop once (thoroughly solve such interview problems)