nextTick's practice in the project

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

Keywords: Vue iOS Android

Added by ivki on Thu, 25 Jun 2020 14:18:22 +0300