>  기사  >  웹 프론트엔드  >  Vue nextTick 메커니즘 사용법에 대한 자세한 설명

Vue nextTick 메커니즘 사용법에 대한 자세한 설명

php中世界最好的语言
php中世界最好的语言원래의
2018-05-15 09:19:351700검색

이번에는 Vue nextTick 메커니즘 사용에 대한 자세한 설명을 가져왔습니다. Vue nextTick 메커니즘을 사용할 때 주의 사항은 무엇입니까?

먼저 Vue 실행 코드를 살펴보겠습니다:

export default {
 data () {
  return {
   msg: 0
  }
 },
 mounted () {
  this.msg = 1
  this.msg = 2
  this.msg = 3
 },
 watch: {
  msg () {
   console.log(this.msg)
  }
 }
}

이 스크립트를 실행한 후 1000m 후에 1, 2, 3의 순서로 인쇄될 것이라고 추측합니다. 그러나 실제로는 한 번만 출력됩니다. 3. 왜 이런 일이 발생합니까? 알아봅시다.

queueWatcher

우리는 메시지를 수신하도록 watch를 정의합니다. 이는 실제로 vm.$watch(keyOrFn, handler, options)와 같이 Vue에서 호출됩니다. $watch는 vm을 초기화할 때 vm에 바인딩된 함수로 Watcher 객체를 생성하는 데 사용됩니다. 그럼 Watcher에서 핸들러가 어떻게 처리되는지 살펴보겠습니다.

this.deep = this.user = this.lazy = this.sync = false
...
 update () {
  if (this.lazy) {
   this.dirty = true
  } else if (this.sync) {
   this.run()
  } else {
   queueWatcher(this)
  }
 }
...

처음에 this.deep = this.user = this.lazy = this.sync = false로 설정합니다. 즉, 업데이트가 트리거되면 queueWatcher 메서드는 다음과 같습니다. 실행됨 :

const queue: Array<Watcher> = []
let has: { [key: number]: ?true } = {}
let waiting = false
let flushing = false
...
export function queueWatcher (watcher: Watcher) {
 const id = watcher.id
 if (has[id] == null) {
  has[id] = true
  if (!flushing) {
   queue.push(watcher)
  } else {
   // if already flushing, splice the watcher based on its id
   // if already past its id, it will be run next immediately.
   let i = queue.length - 1
   while (i > index && queue[i].id > watcher.id) {
    i--
   }
   queue.splice(i + 1, 0, watcher)
  }
  // queue the flush
  if (!waiting) {
   waiting = true
   nextTick(flushSchedulerQueue)
  }
 }
}

여기의 nextTick(flushSchedulerQueue)에 있는 플러시SchedulerQueue 함수는 실제로 감시자의 viewUpdate:

function flushSchedulerQueue () {
 flushing = true
 let watcher, id
 ...
 for (index = 0; index < queue.length; index++) {
  watcher = queue[index]
  id = watcher.id
  has[id] = null
  watcher.run()
  ...
 }
}

입니다. 또한 대기 변수와 관련하여 이는 매우 중요한 플래그로, 이는 flashSchedulerQueue 콜백만 허용되도록 보장합니다. 설정하려면 콜백을 한 번 입력하세요. 다음으로 nextTick 함수에 대해 살펴보겠습니다. nexTick에 대해 이야기하기 전에 Event Loop, microTask, MacroTask에 대한 이해가 필요합니다. Vue nextTick도 이러한 기본 원칙을 주로 사용합니다. 아직 이해하지 못했다면 내 기사 이벤트 루프 소개를 참조하세요. 이제 구현을 살펴보겠습니다.

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]()
  }
 }
 // An asynchronous deferring mechanism.
 // In pre 2.4, we used to use microtasks (Promise/MutationObserver)
 // but microtasks actually has too high a priority and fires in between
 // supposedly sequential events (e.g. #4521, #6690) or even between
 // bubbling of the same event (#6566). Technically setImmediate should be
 // the ideal choice, but it&#39;s not available everywhere; and the only polyfill
 // that consistently queues the callback after all DOM events triggered in the
 // same loop is by using MessageChannel.
 /* istanbul ignore if */
 if (typeof setImmediate !== &#39;undefined&#39; && isNative(setImmediate)) {
  timerFunc = () => {
   setImmediate(nextTickHandler)
  }
 } else if (typeof MessageChannel !== 'undefined' && (
  isNative(MessageChannel) ||
  // PhantomJS
  MessageChannel.toString() === '[object MessageChannelConstructor]'
 )) {
  const channel = new MessageChannel()
  const port = channel.port2
  channel.port1.onmessage = nextTickHandler
  timerFunc = () => {
   port.postMessage(1)
  }
 } else
 /* istanbul ignore next */
 if (typeof Promise !== 'undefined' && isNative(Promise)) {
  // use microtask in non-DOM environments, e.g. Weex
  const p = Promise.resolve()
  timerFunc = () => {
   p.then(nextTickHandler)
  }
 } else {
  // fallback to setTimeout
  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()
  }
  // $flow-disable-line
  if (!cb && typeof Promise !== 'undefined') {
   return new Promise((resolve, reject) => {
    _resolve = resolve
   })
  }
 }
})()

먼저 Vue는 콜백 array을 통해 이벤트 큐를 시뮬레이션합니다. 이벤트 팀은 호출을 실행하기 위해 nextTickHandler 메소드를 전달받고, 실행할 항목은 timeFunc에 의해 결정됩니다. timeFunc의 정의를 살펴보겠습니다.

if (typeof setImmediate !== 'undefined' && isNative(setImmediate)) {
  timerFunc = () => {
   setImmediate(nextTickHandler)
  }
 } else if (typeof MessageChannel !== 'undefined' && (
  isNative(MessageChannel) ||
  // PhantomJS
  MessageChannel.toString() === '[object MessageChannelConstructor]'
 )) {
  const channel = new MessageChannel()
  const port = channel.port2
  channel.port1.onmessage = nextTickHandler
  timerFunc = () => {
   port.postMessage(1)
  }
 } else
 /* istanbul ignore next */
 if (typeof Promise !== 'undefined' && isNative(Promise)) {
  // use microtask in non-DOM environments, e.g. Weex
  const p = Promise.resolve()
  timerFunc = () => {
   p.then(nextTickHandler)
  }
 } else {
  // fallback to setTimeout
  timerFunc = () => {
   setTimeout(nextTickHandler, 0)
  }
 }

timeFunc의 정의 우선순위를 볼 수 있습니다. setImmediate와 MessageChannel을 먼저 정의하여 setTimeout 대신 MacroTask를 생성해야 하는 이유는 무엇입니까? HTML5에서는 setTimeout의 최소 시간 지연이 4ms라고 규정합니다. 이는 이상적인 상황에서 트리거할 수 있는 가장 빠른 비동기 콜백이 4ms임을 의미합니다. Vue는 콜백을 비동기화하고 가능한 한 빨리 호출하는 단 하나의 목적으로 비동기 작업을 시뮬레이션하기 위해 많은 함수를 사용합니다. MessageChannel 및 setImmediate의 지연은 분명히 setTimeout보다 작습니다.

문제 해결

이러한 기초를 염두에 두고 위에서 언급한 문제를 다시 살펴보겠습니다. Vue의 이벤트 메커니즘은 이벤트 큐를 통해 실행을 예약하기 때문에 예약하기 전에 기본 프로세스가 유휴 상태가 될 때까지 기다리므로 다시 업데이트하기 전에 돌아가서 모든 프로세스가 완료될 때까지 기다리세요. 이런 종류의 성능 이점은 명백합니다. 예를 들면 다음과 같습니다.

이제 마운트되면 test 값이 ++loop에 의해 1000번 실행되는 상황이 발생합니다. ++가 트리거될 때마다 setter->Dep->Watcher->update->run이 그에 따라 트리거됩니다. 이때 뷰가 비동기적으로 업데이트되지 않으면 ++는 DOM을 직접 조작하여 매번 뷰를 업데이트하므로 성능이 많이 소모됩니다. 따라서 Vue는 큐를 구현하고 큐에 있는 Watcher의 실행은 다음 Tick(또는 현재 Tick의 마이크로태스크 단계)에서 균일하게 실행됩니다. 동시에, 동일한 ID를 가진 Watcher는 반복적으로 대기열에 추가되지 않으므로 Watcher 실행은 1,000회 실행되지 않습니다. 뷰의 최종 업데이트는 테스트에 해당하는 DOM을 0에서 1000으로 직접 변경합니다. 현재 스택이 실행된 후 다음 Tick(또는 현재 Tick의 마이크로태스크 단계)에서 DOM을 작동하기 위해 뷰를 업데이트하는 작업이 호출되도록 보장되어 성능이 크게 최적화됩니다.

흥미로운 질문

var vm = new Vue({
  el: '#example',
  data: {
    msg: 'begin',
  },
  mounted () {
   this.msg = 'end'
   console.log('1')
   setTimeout(() => { // macroTask
     console.log('3')
   }, 0)
   Promise.resolve().then(function () { //microTask
    console.log('promise!')
   })
   this.$nextTick(function () {
    console.log('2')
   })
 }
})
모두가 이 실행 순서를 알아야 합니다: 1, 약속, 2, 3.

this.msg = 'end'가 먼저 트리거되므로 감시자의 업데이트가 트리거되어 업데이트 작업 콜백을 vue 이벤트 큐로 푸시합니다.

    this.$nextTick은 이벤트 대기열 푸시를 위한 새로운 콜백 함수도 추가합니다. 모두 setImmediate --> MessageChannel --> Promise --> setTimeout을 통해 timeFunc를 정의합니다. Promise.resolve().then은 microTask이므로 약속을 먼저 ​​인쇄합니다.
  1. MessageChannel 및 setImmediate가 지원되는 경우 실행 순서는 setTimeout 이전입니다(IE11/Edge에서는 setImmediate 지연이 1ms 이내일 수 있지만 setTimeout의 최소 지연 시간은 4ms이므로 setImmediate가 setTimeout(0)보다 빠릅니다. 콜백 함수 두 번째로 이벤트 큐에서 콜백 배열을 먼저 수신하므로 2가 출력되고 그 다음에 3

  2. 이 출력됩니다. 그러나 MessageChannel 및 setImmediate가 지원되지 않는 경우 timeFunc는 Promise를 통해 정의됩니다. 2.4 이전 버전의 Vue도 약속을 먼저 ​​실행합니다. 이 상황으로 인해 순서는 1, 2, 약속, 3이 됩니다. this.msg는 먼저 dom 업데이트 함수를 트리거해야 하기 때문에 dom 업데이트 함수는 먼저 비동기 시간 대기열에 대한 콜백에 의해 수신된 다음 Promise.resolve().then(function () { console.log('promise! ')} )와 같은 microTask를 정의한 다음 $nextTick을 정의하면 콜백에 의해 수집됩니다. 우리는 대기열이 선입선출 원칙을 충족하므로 콜백에 의해 수집된 객체가 먼저 실행된다는 것을 알고 있습니다.

Postscript

Vue 소스 코드에 관심이 있다면 여기로 오세요. 더 흥미로운 Vue 규칙 소스 코드 설명

이 기사의 사례를 읽은 후 방법을 마스터했다고 믿습니다. php 중국어 사이트 기타 관련 기사를 주목해 주세요!

추천 자료:

JS는 투명도 그라데이션 기능을 구현합니다

jQuery는 XML 노드 및 속성 구현 단계를 순회합니다

위 내용은 Vue nextTick 메커니즘 사용법에 대한 자세한 설명의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!

성명:
본 글의 내용은 네티즌들의 자발적인 기여로 작성되었으며, 저작권은 원저작자에게 있습니다. 본 사이트는 이에 상응하는 법적 책임을 지지 않습니다. 표절이나 침해가 의심되는 콘텐츠를 발견한 경우 admin@php.cn으로 문의하세요.