NextTick practice in the project

preface

In a project, it is often necessary to immediately display the data in the view layer, but sometimes the page is not immediately displayed due to asynchronous data transfer. In this case, it is necessary to use the nextTick method provided by Vue. The main reason is that Vue’s data view is updated asynchronously.

Vue implementation of responsiveness does not mean that DOM changes immediately after data changes, but rather DOM updates according to a certain policy.

The Event Loop mentioned in this paper is also a point often asked in the front interview. This paper will not elaborate in detail. Students who are interested in this article can refer to this article to understand the Event Loop at one time (completely solve such interview problems).

Hit the pit directory

  • The template case data is displayed in the view
  • Asynchronous data transfer between sibling components
  • $nextTick source implementation parsing

On the pit case

The template case data is displayed in the view

[Bug description] Click reset on the page to render the template view to a view with fixed data

[Bug analysis] It needs to be displayed on the page immediately after clicking, which is a typical scenario where nextTick needs to be applied

[Solution]

There is also a pitfall in the fact that listening to an array type is based on an address, so if you want Vue Watcher to be able to monitor it, you need to follow the array listening methods

    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'}]; }); }Copy the code

Asynchronous data transfer between sibling components

[Bug Description] The input field in the page modification popover needs to be overwritten into the corresponding field. Passing data in using Props does not directly modify the data

In this scenario, data is emitted by the child component to the parent component, and the parent component sends the data to the popup using props. In v-Model, data is obtained asynchronously

[Solution]

This is one of the less common usesEmit, $on] (Blog.csdn.net/qq_42778001…), which makes use of the parent component’s data being deferred to the next tick to the child component, and the child component rendering on the corresponding page in time. There are other methods besides this method, see this article for more details[Fixed] vue parent component passing props 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; })},Copy the code

$nextTick source implementation parsing

Versions 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 inIOS >= 9.3.3 when triggeredin 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 functionqueueNextTick (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
      })
    }
  }
})()
Copy the code

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 (leti = 0; i < copies.length; // 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 inIOS >= 9.3.3 when triggeredin 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 whereNative 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 functionnextTick (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
    })
  }
}
Copy the code

The priority of async in timeFunc is different. After 2.5, there are some differences, but the main issue is whether to expose microtasks and macrotasks (ps: after 2.5, it is 2.6.11).

Versions before 2.5: Promise => MutationObserver => setTimeout

Later versions: setImmediate => MessageChannel => Promise => setTimeout

conclusion

The asynchronous execution mechanism of JS is a knowledge that front-end students must master, among which nextTick is a typical representative. Node also has methods related to nextTick, and the implementation of relevant methods is often asked in interviews. A deep understanding of the basic methods and features of JS is very useful for avoiding pitfalls in front-end development. Every time there is a problem in almost all the interview questions have relevant knowledge to show, laying a good foundation is always a solid foundation for an engineer 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()
    })
}
Copy the code

reference

  • The principle and use of Vue.nextTick
  • Briefly understand nextTick in Vue
  • NextTick source parsing
  • Vue nextTick mechanism
  • Vue source code parsing of nextTick
  • Analysis of Node nextTick
  • Nodejs nextTick and setTimeout
  • Use of this.$nextTick() in vue.js
  • Vue publishes subscriber modeon
  • [Fixed] vue parent component passing props data to child component
  • Figure out the Event Loop in one session (Completely solve this type of interview question)