Vue не обновляет DOM синхронно при каждом изменении данных. Вместо этого он **буферизует все изменения** и применяет их разом в конце текущего «тика» (микрозадачи). Это называется **асинхронным обновлением DOM**.
// В Vue:
count.value = 1
count.value = 2
count.value = 3
// DOM обновится один раз со значением 3, а не три разаТакой подход эффективнее — не нужно перерисовывать DOM при каждом маленьком изменении.
Если вы попытаетесь прочитать DOM-элемент сразу после изменения данных, вы увидите старое значение:
<template>
<div ref="containerEl">
<p v-for="item in items" :key="item">{{ item }}</p>
</div>
<button @click="addItem">Добавить</button>
</template>
<script setup>
const items = ref(['первый'])
const containerEl = ref(null)
function addItem() {
items.value.push('новый')
// ПРОБЛЕМА: DOM ещё не обновился!
console.log(containerEl.value.children.length) // всё ещё 1, не 2
}
</script>Функция nextTick() возвращает Promise, который разрешается **после того, как Vue обновит DOM**:
<script setup>
import { ref, nextTick } from 'vue'
const items = ref(['первый'])
const containerEl = ref(null)
async function addItem() {
items.value.push('новый')
await nextTick() // ждём обновления DOM
// Теперь DOM актуален
console.log(containerEl.value.children.length) // 2
}
</script>Старый вариант — передать функцию напрямую:
nextTick(() => {
// Выполнится после обновления DOM
inputEl.value.scrollIntoView()
})<script setup>
const showInput = ref(false)
const inputEl = ref(null)
async function toggleInput() {
showInput.value = true
await nextTick()
inputEl.value.focus() // фокус только когда input уже в DOM
}
</script>async function sendMessage(text) {
messages.value.push({ id: Date.now(), text })
await nextTick()
// Прокручиваем к последнему сообщению
chatContainer.value.scrollTop = chatContainer.value.scrollHeight
}async function expandSection() {
isExpanded.value = true
await nextTick()
const height = sectionEl.value.offsetHeight
console.log('Высота раскрытой секции:', height)
}nextTick использует Promise.resolve().then() (микрозадачу) или MutationObserver. Vue накапливает все изменения, затем применяет их перед следующим тиком микрозадач. Когда Promise из nextTick разрешается, DOM уже гарантированно обновлён.
Эмуляция очереди обновлений Vue и nextTick через Promise и микрозадачи
// Эмулируем механизм nextTick Vue: буферизация обновлений + flush через микрозадачу
// Очередь отложенных обновлений
let updateQueue = []
let isFlushing = false
let pendingPromise = null
// Псевдо-DOM (хранит актуальное состояние)
const pseudoDOM = {
items: [],
render(items) {
this.items = [...items]
console.log(` [DOM] Перерисован. Элементов: ${this.items.length}. Содержимое: [${this.items.join(', ')}]`)
}
}
// Планировщик: накапливает обновления и сбрасывает их микрозадачей
function queueUpdate(updateFn) {
updateQueue.push(updateFn)
console.log(` [queue] Добавлено обновление. В очереди: ${updateQueue.length}`)
if (!isFlushing) {
isFlushing = true
pendingPromise = Promise.resolve().then(flushQueue)
}
}
function flushQueue() {
console.log(`\n [flush] Применяем ${updateQueue.length} обновлений к DOM...`)
// В реальном Vue здесь происходит re-render компонентов
updateQueue.forEach(fn => fn())
updateQueue = []
isFlushing = false
pendingPromise = null
console.log(' [flush] DOM обновлён.')
}
// nextTick: выполнить после обновления DOM
function nextTick(callback) {
const p = pendingPromise
? pendingPromise.then(callback) // дождаться текущего flush
: Promise.resolve().then(callback) // DOM уже актуален — следующая микрозадача
return p
}
// --- Симуляция работы компонента ---
const reactiveItems = ['один']
async function main() {
console.log('=== Сценарий 1: Чтение DOM до nextTick ===')
// Добавляем элемент (как items.value.push('два') в Vue)
reactiveItems.push('два')
queueUpdate(() => pseudoDOM.render(reactiveItems))
console.log(' [sync] DOM сейчас (до nextTick):', pseudoDOM.items)
// Старое значение — DOM ещё не обновлён
await nextTick(() => {
console.log(' [nextTick] DOM после обновления:', pseudoDOM.items)
})
console.log('\n=== Сценарий 2: Несколько изменений — один ре-рендер ===')
reactiveItems.push('три')
queueUpdate(() => console.log(' [render] Первое изменение обработано'))
reactiveItems.push('четыре')
queueUpdate(() => console.log(' [render] Второе изменение обработано'))
reactiveItems.push('пять')
queueUpdate(() => {
pseudoDOM.render(reactiveItems)
console.log(' [render] Финальный рендер с ТРЕМЯ изменениями сразу')
})
console.log(' [sync] Добавили 3 элемента, но flush ещё не произошёл')
await nextTick()
console.log(' [nextTick] Все три изменения применены за один проход')
console.log('\n=== Итог ===')
console.log('Vue буферизует изменения и применяет их все разом.')
console.log('nextTick() позволяет дождаться этого момента.')
}
main()Vue не обновляет DOM синхронно при каждом изменении данных. Вместо этого он **буферизует все изменения** и применяет их разом в конце текущего «тика» (микрозадачи). Это называется **асинхронным обновлением DOM**.
// В Vue:
count.value = 1
count.value = 2
count.value = 3
// DOM обновится один раз со значением 3, а не три разаТакой подход эффективнее — не нужно перерисовывать DOM при каждом маленьком изменении.
Если вы попытаетесь прочитать DOM-элемент сразу после изменения данных, вы увидите старое значение:
<template>
<div ref="containerEl">
<p v-for="item in items" :key="item">{{ item }}</p>
</div>
<button @click="addItem">Добавить</button>
</template>
<script setup>
const items = ref(['первый'])
const containerEl = ref(null)
function addItem() {
items.value.push('новый')
// ПРОБЛЕМА: DOM ещё не обновился!
console.log(containerEl.value.children.length) // всё ещё 1, не 2
}
</script>Функция nextTick() возвращает Promise, который разрешается **после того, как Vue обновит DOM**:
<script setup>
import { ref, nextTick } from 'vue'
const items = ref(['первый'])
const containerEl = ref(null)
async function addItem() {
items.value.push('новый')
await nextTick() // ждём обновления DOM
// Теперь DOM актуален
console.log(containerEl.value.children.length) // 2
}
</script>Старый вариант — передать функцию напрямую:
nextTick(() => {
// Выполнится после обновления DOM
inputEl.value.scrollIntoView()
})<script setup>
const showInput = ref(false)
const inputEl = ref(null)
async function toggleInput() {
showInput.value = true
await nextTick()
inputEl.value.focus() // фокус только когда input уже в DOM
}
</script>async function sendMessage(text) {
messages.value.push({ id: Date.now(), text })
await nextTick()
// Прокручиваем к последнему сообщению
chatContainer.value.scrollTop = chatContainer.value.scrollHeight
}async function expandSection() {
isExpanded.value = true
await nextTick()
const height = sectionEl.value.offsetHeight
console.log('Высота раскрытой секции:', height)
}nextTick использует Promise.resolve().then() (микрозадачу) или MutationObserver. Vue накапливает все изменения, затем применяет их перед следующим тиком микрозадач. Когда Promise из nextTick разрешается, DOM уже гарантированно обновлён.
Эмуляция очереди обновлений Vue и nextTick через Promise и микрозадачи
// Эмулируем механизм nextTick Vue: буферизация обновлений + flush через микрозадачу
// Очередь отложенных обновлений
let updateQueue = []
let isFlushing = false
let pendingPromise = null
// Псевдо-DOM (хранит актуальное состояние)
const pseudoDOM = {
items: [],
render(items) {
this.items = [...items]
console.log(` [DOM] Перерисован. Элементов: ${this.items.length}. Содержимое: [${this.items.join(', ')}]`)
}
}
// Планировщик: накапливает обновления и сбрасывает их микрозадачей
function queueUpdate(updateFn) {
updateQueue.push(updateFn)
console.log(` [queue] Добавлено обновление. В очереди: ${updateQueue.length}`)
if (!isFlushing) {
isFlushing = true
pendingPromise = Promise.resolve().then(flushQueue)
}
}
function flushQueue() {
console.log(`\n [flush] Применяем ${updateQueue.length} обновлений к DOM...`)
// В реальном Vue здесь происходит re-render компонентов
updateQueue.forEach(fn => fn())
updateQueue = []
isFlushing = false
pendingPromise = null
console.log(' [flush] DOM обновлён.')
}
// nextTick: выполнить после обновления DOM
function nextTick(callback) {
const p = pendingPromise
? pendingPromise.then(callback) // дождаться текущего flush
: Promise.resolve().then(callback) // DOM уже актуален — следующая микрозадача
return p
}
// --- Симуляция работы компонента ---
const reactiveItems = ['один']
async function main() {
console.log('=== Сценарий 1: Чтение DOM до nextTick ===')
// Добавляем элемент (как items.value.push('два') в Vue)
reactiveItems.push('два')
queueUpdate(() => pseudoDOM.render(reactiveItems))
console.log(' [sync] DOM сейчас (до nextTick):', pseudoDOM.items)
// Старое значение — DOM ещё не обновлён
await nextTick(() => {
console.log(' [nextTick] DOM после обновления:', pseudoDOM.items)
})
console.log('\n=== Сценарий 2: Несколько изменений — один ре-рендер ===')
reactiveItems.push('три')
queueUpdate(() => console.log(' [render] Первое изменение обработано'))
reactiveItems.push('четыре')
queueUpdate(() => console.log(' [render] Второе изменение обработано'))
reactiveItems.push('пять')
queueUpdate(() => {
pseudoDOM.render(reactiveItems)
console.log(' [render] Финальный рендер с ТРЕМЯ изменениями сразу')
})
console.log(' [sync] Добавили 3 элемента, но flush ещё не произошёл')
await nextTick()
console.log(' [nextTick] Все три изменения применены за один проход')
console.log('\n=== Итог ===')
console.log('Vue буферизует изменения и применяет их все разом.')
console.log('nextTick() позволяет дождаться этого момента.')
}
main()Реализуй упрощённую версию `nextTick`. Напиши функцию `createScheduler()`, которая возвращает объект с методами: `schedule(fn)` — добавляет функцию в очередь и планирует сброс через `Promise.resolve()`, `nextTick(callback)` — возвращает Promise, который разрешается после сброса очереди. Флаш очереди должен происходить один раз, даже если `schedule` вызвали несколько раз подряд.
Сохраняй Promise из `Promise.resolve().then(flush)` в переменную `flushPromise`. В `nextTick` используй `flushPromise ? flushPromise.then(callback) : Promise.resolve().then(callback)`. В `flush` обнуляй `flushing` и `flushPromise` после выполнения очереди.
Токены для AI-помощника закончились
Купи токены чтобы задавать вопросы AI прямо в уроке