Простое понимание nextTick в Vue

внешний интерфейс JavaScript Vue.js

во ВьюnextTickКогда дело доходит до асинхронного обновления DOM во Vue, это кажется очень интересным, и я специально узнал об этом. из них оnextTickИсходный код включает в себя много знаний, многие из которых не очень хорошо поняты.Пока что я представлю его в соответствии с некоторыми из моих собственных идей.nextTick.

1. Пример

Давайте рассмотрим пример, чтобы узнать об обновлениях DOM в Vue иnextTickэффект.

шаблон

<div class="app">
  <div ref="msgDiv">{{msg}}</div>
  <div v-if="msg1">Message got outside $nextTick: {{msg1}}</div>
  <div v-if="msg2">Message got inside $nextTick: {{msg2}}</div>
  <div v-if="msg3">Message got outside $nextTick: {{msg3}}</div>
  <button @click="changeMsg">
    Change the Message
  </button>
</div>

Экземпляр Vue

new Vue({
  el: '.app',
  data: {
    msg: 'Hello Vue.',
    msg1: '',
    msg2: '',
    msg3: ''
  },
  methods: {
    changeMsg() {
      this.msg = "Hello world."
      this.msg1 = this.$refs.msgDiv.innerHTML
      this.$nextTick(() => {
        this.msg2 = this.$refs.msgDiv.innerHTML
      })
      this.msg3 = this.$refs.msgDiv.innerHTML
    }
  }
})

до щелчка

После нажатия

Из рисунка видно, что содержимое, отображаемое msg1 и msg3, еще до преобразования, а содержимое, отображаемое msg2, — после преобразования. Основная причина заключается в том, что обновления DOM в Vue являются асинхронными (объясняется позже).

2. Сценарии применения

Узнайте нижеnextTickОсновные сценарии применения и причины.

  • в жизненном цикле Vuecreated()Операции DOM, выполняемые функцией ловушки, должны быть помещены вVue.nextTick()в функции обратного вызова

существуетcreated()Когда функция ловушки выполняется, DOM фактически не выполняет никакого рендеринга, а операция DOM в это время бесполезна, поэтому здесь должен быть размещен js-код операции DOM.Vue.nextTick()в функции обратного вызова. Ему соответствуетmounted()Функция ловушки, потому что все монтирование и рендеринг DOM были завершены, когда функция ловушки выполняется, и любые операции DOM в функции ловушки в это время не будут проблемой.

  • Когда операция должна выполняться после изменения данных, и эта операция должна использовать структуру DOM, которая изменяется при изменении данных, эта операция должна быть помещена вVue.nextTick()в функции обратного вызова.

Конкретные причины подробно описаны в официальной документации Vue:

Vue выполняет обновления DOM асинхронно. Как только будут обнаружены изменения данных, Vue откроет очередь и буферизует все изменения данных, которые происходят в том же цикле событий. Если один и тот же наблюдатель запускается несколько раз, он будет помещен в очередь только один раз. Эта дедупликация во время буферизации важна, чтобы избежать ненужных вычислений и манипуляций с DOM. Затем, в следующем цикле событий «тик», Vue очищает очередь и выполняет фактическую (дедублированную) работу. Vue внутренне пытается использовать натив для асинхронных очередейPromise.thenа такжеMessageChannel, если среда выполнения не поддерживает это, используйтеsetTimeout(fn, 0)заменять.

Например, когда вы устанавливаетеvm.someData = 'new value', компонент не будет повторно отображаться немедленно. Когда очередь очищается, компонент обновляется на следующем «тике», когда очередь цикла событий пуста. В большинстве случаев нам не нужно заботиться об этом процессе, но если вы хотите что-то сделать после обновления состояния DOM, это может быть немного сложно. В то время как Vue.js обычно поощряет разработчиков мыслить «управляемыми данными» и избегать прямого контакта с DOM, иногда мы так и поступаем. Чтобы дождаться, пока Vue завершит обновление DOM после изменения данных, вы можете использовать сразу после изменения данныхVue.nextTick(callback). Таким образом, функция обратного вызова будет вызываться после завершения обновления DOM.

три,nextTickАнализ исходного кода

эффект

Vue.nextTickИспользуется для задержки выполнения фрагмента кода, принимает 2 параметра (функция обратного вызова и контекст, в котором функция обратного вызова выполняется), если функция обратного вызова не указана, возвращаетсяpromiseобъект.

исходный код

/**
 * 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
      })
    }
  }
})()

Во-первых, понятьnextTickТри важные переменные, определенные в .

  • callbacks

Используется для хранения всех функций обратного вызова, которые необходимо выполнить.

  • pending

Используется, чтобы отметить, выполняется ли функция обратного вызова

  • timerFunc

Используется для запуска выполнения функции обратного вызова

Далее узнайте оnextTickHandler()функция.

function nextTickHandler () {
    pending = false
    const copies = callbacks.slice(0)
    callbacks.length = 0
    for (let i = 0; i < copies.length; i++) {
      copies[i]()
    }
  }

Эта функция используется для выполненияcallbacksВсе функции обратного вызова, хранящиеся в .

Следующим шагом является назначение метода триггера дляtimerFunc.

  • Сначала определите, поддерживаются ли обещания изначально, и если да, используйте обещания для запуска и выполнения функций обратного вызова;
  • В противном случае, если поддерживается MutationObserver, создается экземпляр объекта-наблюдателя, и при изменении наблюдаемого текстового узла запускаются все функции обратного вызова для выполнения.
  • Если не поддерживается, используйте setTimeout, чтобы установить задержку на 0.

Ну наконец тоqueueNextTickфункция. потому чтоnextTickявляется непосредственной функцией, поэтомуqueueNextTickФункция — это возвращаемая функция, которая принимает параметры, переданные пользователем, и используется для хранения функции обратного вызова в обратных вызовах.

На картинке выше весь процесс выполнения, ключ кроется вtimeFunc(), функция играет роль отложенного исполнения.

Из приведенного выше введения видно, чтоtimeFunc()Есть три реализации.

  • Promise
  • MutationObserver
  • setTimeout

вPromiseа такжеsetTimeoutХорошо известно, что это асинхронная задача, которая будет вызывать определенные функции после синхронной задачи и асинхронной задачи, обновляющей DOM.

Далее речь пойдет оMutationObserver.

MutationObserverНовый API в HTML5, интерфейс, используемый для отслеживания изменений DOM. Он может отслеживать удаление дочерних узлов, изменение атрибутов, изменение текстового содержимого и т. д. в объекте DOM. Процесс вызова прост, но немного необычен: сначала нужно привязать к нему callback:

var mo = new MutationObserver(callback)

ДаваяMutationObserverКонструктор прохода в обратном вызове можно получитьMutationObserverнапример, этот обратный вызов будет вызван вMutationObserverЗапускается, когда экземпляр ожидает изменения.

на этот раз ты просто даешьMutationObserverЭкземпляр привязан к обратному вызову.Еще не установлено, какой DOM отслеживать, следить за удалением узла или следить за изменением атрибута. и позвони егоobserverспособ сделать это:

var domTarget = 你想要监听的dom节点
mo.observe(domTarget, {
      characterData: true //说明监听文本内容的修改。
})

существуетnextTickсерединаMutationObserverфункционировать, как показано выше. После прослушивания обновления DOM вызывается функция обратного вызова.

На самом деле использоватьMutationObserverПричина в том, чтоnextTickХотите, чтобы асинхронный API выполнял асинхронные обратные вызовы, которые я хочу выполнить после завершения выполнения текущего синхронного кода, включаяPromiseа такжеsetTimeoutВсе по этой причине. Углубление также включаетmicrotaskЕсли вы пока не понимаете содержание, я не буду вводить его подробно.