ПредыдущийПринципы адаптивного графического интерфейса VueВ этой статье мы поняли процесс рендеринга Vue через блок-схемы 9. Я считаю, что у каждого есть определенное понимание процесса рендеринга Vue.В этой статье мы сосредоточимся на модуле принципа асинхронного обновления Vue.
В этом документе анализируется обновление данных из Vue, асинхронный процесс уведомления об обновлении. Представление Watcher, т. е. оранжевая часть на фиг.

Давайте сначала рассмотрим несколько объектов на рисунке:
- Объект данных: объект, возвращаемый методом данных в Vue.
- Объект Dep: Каждое свойство Data создаст Dep для сбора всех объектов Watcher, которые используют эти данные.
- Объект Watcher: в основном используется для рендеринга DOM.
Далее начинаем анализировать этот процесс.
Принцип асинхронного обновления Vue DOM
Многие студенты знают, что обновление данных Vue является асинхронным, что означает, что после того, как мы закончим изменение данных, мы не сможем немедленно изменить элементы DOM.
Например:
<template>
<div>
<span id="text">{{ message }}</span>
<button @click="changeData">
changeData
</button>
</div>
</template>
<script>
export default {
data() {
return {
message: "hello",
};
},
methods: {
changeData() {
this.message = "hello world";
const textContent = document.getElementById("text").textContent;
// 直接获取,不是最新的
console.log(textContent === "hello world"); // false
// $nextTick 回调中,是最新的
this.$nextTick(() => {
const textContent = document.getElementById("text").textContent;
console.warn(textContent === "hello world"); // true
});
},
},
};
</script>
Когда мы доберемся до настоящего элемента DOM?
Ответ: В обратном вызове Vue nextTick.
Это подробно описано на официальном сайте Vue, но задумывались ли вы когда-нибудь, почему Vue нужно передать метод nextTick, чтобы получить последнюю версию DOM?
Имея в виду этот вопрос, давайте посмотрим непосредственно на исходный код.
// 当一个 Data 更新时,会依次执行以下代码
// 1. 触发 Data.set
// 2. 调用 dep.notify
// 3. Dep 会遍历所有相关的 Watcher 执行 update 方法
class Watcher {
// 4. 执行更新操作
update() {
queueWatcher(this);
}
}
const queue = [];
function queueWatcher(watcher: Watcher) {
// 5. 将当前 Watcher 添加到异步队列
queue.push(watcher);
// 6. 执行异步队列,并传入回调
nextTick(flushSchedulerQueue);
}
// 更新视图的具体方法
function flushSchedulerQueue() {
let watcher, id;
// 排序,先渲染父节点,再渲染子节点
// 这样可以避免不必要的子节点渲染,如:父节点中 v-if 为 false 的子节点,就不用渲染了
queue.sort((a, b) => a.id - b.id);
// 遍历所有 Watcher 进行批量更新。
for (index = 0; index < queue.length; index++) {
watcher = queue[index];
// 更新 DOM
watcher.run();
}
}
В соответствии с приведенным выше кодом мы можем нарисовать такую блок-схему:

Как видно на рисунке, VUE не обновляет представление напрямую, но Watcher обновлений добавляется в очередь Queue, а затем в NEXTTICK передается конкретный метод обновления flushschedulerQueue.
Далее давайте проанализируем nextTick.
const callbacks = [];
let timerFunc;
function nextTick(cb?: Function, ctx?: Object) {
let _resolve;
// 1.将传入的 flushSchedulerQueue 方法添加到回调数组
callbacks.push(() => {
cb.call(ctx);
});
// 2.执行异步任务
// 此方法会根据浏览器兼容性,选用不同的异步策略
timerFunc();
}
Как видите, функция nextTick очень проста, она просто добавляет входящий поток flushSchedulerQueue в массив callbacks, а затем выполняет метод timerFunc.
Далее давайте проанализируем метод timerFunc.
let timerFunc;
// 判断是否兼容 Promise
if (typeof Promise !== "undefined") {
timerFunc = () => {
Promise.resolve().then(flushCallbacks);
};
// 判断是否兼容 MutationObserver
// https://developer.mozilla.org/zh-CN/docs/Web/API/MutationObserver
} else if (typeof MutationObserver !== "undefined") {
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);
};
// 判断是否兼容 setImmediate
// 该方法存在一些 IE 浏览器中
} else if (typeof setImmediate !== "undefined") {
// 这是一个宏任务,但相比 setTimeout 要更好
timerFunc = () => {
setImmediate(flushCallbacks);
};
} else {
// 如果以上方法都不知道,使用 setTimeout 0
timerFunc = () => {
setTimeout(flushCallbacks, 0);
};
}
// 异步执行完后,执行所有的回调方法,也就是执行 flushSchedulerQueue
function flushCallbacks() {
for (let i = 0; i < copies.length; i++) {
callbacks[i]();
}
}
Как видите, timerFunc — это асинхронный метод, созданный на основе совместимости с браузером.После его выполнения он будет вызывать метод flushSchedulerQueue для выполнения определенных обновлений DOM.
На этом этапе анализа мы можем получить общую блок-схему.

Далее, давайте улучшим логику суждений.
- Определите именный идентификатор и избегайте добавления того же наблюдения в очередь.
- Определите флаг ожидания и позвольте всем Наблюдателям обновиться в течение одного тика.
- Оценка сброса флага и обработка нового наблюдателя, который может быть сгенерирован при рендеринге наблюдателя.
- Такие как: срабатывание условия v-if, новый рендеринг Watcher.
На основании вышеизложенных суждений окончательная блок-схема выглядит следующим образом:

Наконец, мы анализируем, почемуthis.$nextTickМожно получить обновленный DOM?
// 我们使用 this.$nextTick 其实就是调用 nextTick 方法
Vue.prototype.$nextTick = function (fn: Function) {
return nextTick(fn, this);
};
Как видите, звонитеthis.$nextTickНа самом деле метод nextTick на рисунке вызывается для выполнения функции обратного вызова в асинхронной очереди. В соответствии с принципом «первым пришел — первым обслужен» асинхронная очередь обновления, запускаемая изменением данных, будет выполняться первой, после завершения выполнения будет сгенерирована новая DOM, а затем выполнена.this.$nextTickКогда вызывается функция обратного вызова, можно получить обновленный элемент DOM.
Потому что Nextick просто прошелPromise,SetTimeoutи другие методы для моделирования асинхронной задачи, поэтому вы также можете вручную выполнить асинхронную задачу для достижения того же эффекта, что и this.$nextTick.
this.message = "hello world";
// 手动执行一个异步任务,也能获取最新的 DOM
Promise.resolve().then(() => {
const textContent = document.getElementById("text").textContent;
console.log(textContent === "hello world"); // true
});
setTimeout(() => {
const textContent = document.getElementById("text").textContent;
console.log(textContent === "hello world"); // true
});
Мышление и резюме
В этой статье представлен принцип асинхронного обновления Vue с точки зрения исходного кода, давайте кратко рассмотрим его.
- Когда данные в Vue изменяются, все наблюдатели, связанные с этими данными, будут обновлены.
- Сначала наблюдатель все присоединится к очереди очереди.
- Затем вызовите метод nextTick для выполнения асинхронных задач.
Наконец, если у вас есть какие-либо мысли по этому поводу, пожалуйста, оставьте комментарий!
