во Вью
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
Основные сценарии применения и причины.
- в жизненном цикле Vue
created()
Операции 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
Если вы пока не понимаете содержание, я не буду вводить его подробно.