扯皮
❝
在最近的面试中,不少面试官问道简单聊一下 vue 中的
nextTick吧,nextTick是个啥,这篇文章咱来好好聊聊!
用通俗易懂的方式理解的话,可以说是由于 Vue 的响应式变量是异步更新 DOM 的,当你的数据变量发生改动时,不能第一时间更新到最新的 DOM 中,这时候呢,这个nextTick就可以拿到最新的 DOM。
示例 demo
在这个例子中,我们在<div> 标签上添加了ref="demoref",这是 Vue 的基本语法,鉴于有了 TS 的加持下,可以轻松的获取到标签的DOM结构,之后,我们用 log 打印一下该 DOM 结构
Tips小提示:ref<HTMLElement>()开发当中加入TS规范有助于代码提示
<template>
<div class="" ref="demoref">{{ activeNames }}</div>
</template>
<script setup lang="ts">
const activeNames = ref(1);
const increment = async () => {};
const demoref = ref<HTMLElement>();
// DOM还未更新
console.log(demoref.value);
// 异步微任务
nextTick(() => {
// DOM更新了
console.log("NextTick: ", demoref.value);
});
</script>
<style scoped lang="scss"></style>
在 macrotask 中复现
nextTick(() => {
// DOM更新了
console.log("NextTick: ", demoref.value);
});
setTimeout(() => {
// 宏任务队列中可以拿到更新后的DOM结构数据
console.log("macortask: ", demoref.value);
}, 1000);
❝
问题一:在 vue 中为什么不能直接拿到 DOM 结构?
出自于 VUE 的生命周期和渲染机制的原因,组件的渲染过程是异步的,需要等待组件渲染完成后才可以获取 DOM 结构数据,以下几点:
[1] Vue的生命周期:mounted()钩子是组件挂在到 DOM 上之后调用的,这时才能确保 DOM 已经加载完成。
[2] 虚拟DOM(简称 VDOM)的异步更新: 用虚拟 DOM 进行 DOM 更新,意味着组件的 DOM 结构不是即刻更新的而是通过异步更新方式进行更新,so~, 需要等待更新完成后才能获取到最新的 DOM 结构。
[3] 数据驱动视图: Vue 是数据驱动的框架,当数据发生改变时,会重新渲染视图,意味着在数据更新后才能获取更新后的 DOM 结构。
Vue 整个生命周期
V3 生命周期列表
| 执行顺序 | 名称 | 作用 |
|---|---|---|
| 1 | onBeforeMount() | 在组件挂载到 DOM 之前执行 |
| 2 | onMounted() | 在组件挂载到 DOM 之后执行 |
| 3 | onBeforeUpdate() | 在响应式数据发生变化,且组件重新渲染前执行 |
| 4 | onUpdated() | 在组件重新渲染并更新 DOM 之后执行 |
| 5 | onBeforeUnmount() | 在组件卸载之前执行 |
| 6 | onUnmounted() | 在组件卸载之后执行 |
示例
<div class="" ref="demoref">{{ activeNames }}</div>
<el-button @click="increment()">更新DOM</el-button>
<script setup lang="ts">
onUpdated(() => {
console.log("onUpdated: ", demoref.value);
});
onMounted(() => {
console.log("onMounted: ", demoref.value);
// 挂载完成后 模拟响应式数据更新
activeNames.value = 33;
});
onBeforeUpdate(() => {
console.log("onBeforeUpdate: ", demoref.value);
});
onUnmounted(() => {
console.log("onUnmounted: ", demoref.value);
});
onBeforeMount(() => {
console.log("onBeforeMount: ", demoref.value);
});
onBeforeUnmount(() => {
console.log("onBeforeUnmount: ", demoref.value);
});
nextTick(() => {
console.log("NextTick: ", demoref.value);
});
setTimeout(() => {
console.log("macortask: ", demoref.value);
}, 1000);
<script>
nextTick 原理
源代码 https://github.com/vuejs/vue/blob/main/src/core/util/next-tick.ts
if (typeof Promise !== 'undefined' && isNative(Promise)) {
const p = Promise.resolve()
timerFunc = () => {
p.then(flushCallbacks)
if (isIOS) setTimeout(noop)
}
isUsingMicroTask = true
} else if (
!isIE &&
typeof MutationObserver !== 'undefined' &&
(isNative(MutationObserver) ||
MutationObserver.toString() === '[object MutationObserverConstructor]')
) {
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)
}
isUsingMicroTask = true
} else if (typeof setImmediate !== 'undefined' && isNative(setImmediate)) {
timerFunc = () => {
setImmediate(flushCallbacks)
}
} else {
timerFunc = () => {
setTimeout(flushCallbacks, 0)
}
}
执行顺序的优先级如下:
- Promise.resolve().then:微任务
- MutationObserver:微任务
- setImmediate:宏任务
- setTimeout:宏任务
如果调用nextTick()时,会先检查原生Promise 【微任务(Microtask)】 是否可用,如果可用的话,将会优先设置在Microtask Queue中异步执行。
若原生Promise不可用时,调用MutationObserver 【微任务(Microtask)】 (可以监听 DOM 变化的 Web API)加入到异步队列,
若 DOM 的MutationObserver也不可用时,调用setImmediate 【宏任务(Macrotask)】 ,是否可用,它比 setTimeout 更快地执行回调,因为它不受最小延迟时间的限制。
手写一个nextTick()
我们已经明白了nextTick的作用,在dom结构全部渲染完成后执行。而nextTick就是接受一个回调之后执行,我们可以尝试用上文中的MutationObserve做出监听DOM变化的功能。
const useMyNextTick = (func: any) => {
let app: any = document.getElementById("app");
var observerOptions = {
childList: true,
attributes: true,
subtree: true,
};
let observer = new MutationObserver((el) => {
console.log(el);
func();
});
observer.observe(app, observerOptions);
};
通过document.getElementById('app')去拿到一个DOM结构,然后写一个监听的配置项。 创建一个监听DOM的对象observer。
而当我们监听的这个app有变更时,就会触发里面的回调函数。
这样我们就简单的实现了一个nextTick.
结尾
写文章不易,如果帮助到了小伙伴们,可以给本文点赞收藏评论三连呀。有不懂的地方欢迎到评论区留言,我们及时互动。