各位前端的英雄好汉们,早上/下午/晚上好!我是今天的主讲人,咱们今天聊聊Vue 3源码里那些你可能没注意到的宝藏,特别是VueUse里onMounted
和onUnmounted
这两个看似简单,实则蕴含着Vue生命周期精髓的hooks。别紧张,咱们不搞那些枯燥的理论,争取用最轻松幽默的方式,把这些东西给扒个底朝天。
开场白:VueUse是啥?为啥要聊它?
首先,咱们得搞清楚VueUse是个啥玩意儿。简单来说,它就是一个Vue组合式函数(Composition API)的工具库,里面包含了各种各样的实用hooks,比如咱们今天要讲的onMounted
和onUnmounted
。
为啥要聊VueUse里的这两个hooks?因为它们是Vue生命周期钩子的封装,理解它们的实现原理,能让你更深入地了解Vue组件的渲染和卸载过程,写出更高性能、更健壮的Vue应用。而且,面试的时候如果能把这些东西侃侃而谈,绝对能给面试官留下深刻的印象,直接加分!
第一部分:onMounted
– 组件挂载的那一瞬间
onMounted
,顾名思义,就是在组件挂载到DOM之后执行的钩子函数。在Vue 2里,我们用的是mounted
选项,而在Vue 3里,我们用onMounted
这个组合式函数来达到同样的效果。
1.1 onMounted
的基本用法
先来看一个最简单的例子:
<template>
<div>
<h1>Hello, World!</h1>
</div>
</template>
<script setup>
import { onMounted } from 'vue';
onMounted(() => {
console.log('组件挂载了!');
// 这里可以执行一些需要在组件挂载后才能进行的操作,比如操作DOM
});
</script>
这段代码很简单,就是在组件挂载后,控制台会打印出"组件挂载了!"。
1.2 onMounted
的源码剖析
那么,Vue内部是如何实现onMounted
的呢? 咱们简化一下,核心逻辑大概是这样的(别害怕,代码不长):
// 简化版的 onMounted 实现
function onMounted(hook, target = currentInstance) {
if (target) {
injectHook(LifecycleHooks.MOUNTED, hook, target);
} else {
console.warn('onMounted() 只能在 setup() 或生命周期钩子中使用。');
}
}
// LifecycleHooks 是一个枚举,定义了各种生命周期钩子的类型
const LifecycleHooks = {
BEFORE_MOUNT: 'bm',
MOUNTED: 'm',
BEFORE_UPDATE: 'bu',
UPDATED: 'u',
BEFORE_UNMOUNT: 'bum',
UNMOUNTED: 'um',
ERROR_CAPTURED: 'ec',
RENDER_TRACKED: 'rtg',
RENDER_TRIGGERED: 'rtc',
ACTIVATED: 'a',
DEACTIVATED: 'da',
SERVER_PREFETCH: 'sp'
};
// 简化版的 injectHook 实现
function injectHook(type, hook, target) {
if (target) {
const hooks = target[type] || (target[type] = []); // 如果不存在,创建一个数组
const wrappedHook = (...args) => {
// 执行用户传入的hook函数
hook(...args);
};
hooks.push(wrappedHook);
}
}
这段代码做了什么呢?
onMounted(hook, target)
: 接收一个hook
函数(也就是用户传入的回调函数)和一个可选的target
参数(默认是当前的组件实例)。injectHook(LifecycleHooks.MOUNTED, hook, target)
: 将hook
函数注入到组件实例的LifecycleHooks.MOUNTED
钩子列表中。
关键在于injectHook
函数。它会把用户的hook
函数放到组件实例的m
(即MOUNTED
)属性对应的数组里。 当组件挂载的时候,Vue会遍历这个数组,依次执行里面的函数。
1.3 深入理解currentInstance
代码里的currentInstance
是啥? 这玩意儿是Vue内部用来追踪当前组件实例的。 在setup()
函数或者生命周期钩子函数里,currentInstance
会被正确设置,指向当前的组件实例。 所以,onMounted
才能正确地把你的回调函数注册到对应的组件上。
如果在setup
之外使用onMounted
,currentInstance
可能为null
,Vue就会发出警告。
1.4 实际应用场景
onMounted
的应用场景非常广泛,比如:
- 获取DOM元素: 组件挂载后,DOM元素才真正渲染出来,你才能使用
document.getElementById
或者ref
来获取它们。
<template>
<div>
<input ref="myInput" type="text">
</div>
</template>
<script setup>
import { ref, onMounted } from 'vue';
const myInput = ref(null);
onMounted(() => {
myInput.value.focus(); // 组件挂载后,让输入框自动获得焦点
});
</script>
- 发起网络请求: 有些数据需要在组件挂载后才能获取,比如根据用户ID获取用户信息。
<template>
<div>
<h1>欢迎,{{ userName }}!</h1>
</div>
</template>
<script setup>
import { ref, onMounted } from 'vue';
import axios from 'axios'; // 假设你用的是axios
const userName = ref('加载中...');
onMounted(async () => {
try {
const response = await axios.get('/api/user/123'); // 假设用户ID是123
userName.value = response.data.name;
} catch (error) {
console.error('获取用户信息失败:', error);
userName.value = '获取失败';
}
});
</script>
- 初始化第三方库: 有些第三方库需要在DOM元素存在的情况下才能初始化,比如地图组件、图表组件等。
第二部分:onUnmounted
– 组件卸载的那一瞬间
onUnmounted
,和onMounted
相对应,就是在组件从DOM中卸载之前执行的钩子函数。 在Vue 2里,我们用的是beforeDestroy
和destroyed
选项,而在Vue 3里,我们用onBeforeUnmount
和onUnmounted
这两个组合式函数。 onBeforeUnmount
在卸载前调用,onUnmounted
在卸载后调用。
2.1 onUnmounted
的基本用法
<template>
<div>
<h1>再见,世界!</h1>
</div>
</template>
<script setup>
import { onUnmounted } from 'vue';
onUnmounted(() => {
console.log('组件卸载了!');
// 这里可以执行一些清理工作,比如取消定时器、解绑事件监听器
});
</script>
这段代码很简单,就是在组件卸载后,控制台会打印出"组件卸载了!"。
2.2 onUnmounted
的源码剖析
和onMounted
类似,onUnmounted
的实现原理也很简单。
// 简化版的 onUnmounted 实现
function onUnmounted(hook, target = currentInstance) {
if (target) {
injectHook(LifecycleHooks.UNMOUNTED, hook, target);
} else {
console.warn('onUnmounted() 只能在 setup() 或生命周期钩子中使用。');
}
}
// (LifecycleHooks 的定义和 injectHook 的实现和前面 onMounted 的例子一样)
可以看到,onUnmounted
和onMounted
的代码几乎一样,唯一的区别就是LifecycleHooks
的类型不同。 onUnmounted
使用的是LifecycleHooks.UNMOUNTED
,也就是um
。
当组件卸载的时候,Vue会遍历组件实例的um
属性对应的数组,依次执行里面的函数。
2.3 实际应用场景
onUnmounted
的应用场景也很重要,主要用于清理工作,防止内存泄漏。
- 取消定时器: 如果在组件挂载后启动了定时器,在组件卸载时一定要取消定时器。
<template>
<div>
<h1>{{ count }}</h1>
</div>
</template>
<script setup>
import { ref, onMounted, onUnmounted } from 'vue';
const count = ref(0);
let timer = null;
onMounted(() => {
timer = setInterval(() => {
count.value++;
}, 1000);
});
onUnmounted(() => {
clearInterval(timer); // 组件卸载时,取消定时器
timer = null; // 最好把 timer 设置为 null
console.log('定时器已清除!');
});
</script>
- 解绑事件监听器: 如果在组件挂载后绑定了事件监听器,在组件卸载时一定要解绑事件监听器。
<template>
<div>
<button @click="handleClick">点击我</button>
</div>
</template>
<script setup>
import { onMounted, onUnmounted } from 'vue';
const handleClick = () => {
console.log('按钮被点击了!');
};
onMounted(() => {
window.addEventListener('click', handleClick); // 监听全局点击事件
});
onUnmounted(() => {
window.removeEventListener('click', handleClick); // 组件卸载时,解绑事件监听器
console.log('事件监听器已移除!');
});
</script>
- 取消网络请求: 如果在组件挂载后发起了网络请求,在组件卸载时可以取消未完成的请求。(需要配合
axios
的cancelToken
等机制) - 释放资源: 如果组件使用了某些需要手动释放的资源,比如WebSocket连接,在组件卸载时一定要释放这些资源。
第三部分:onBeforeUnmount
– 卸载前的最后准备
onBeforeUnmount
是在组件卸载之前调用的,它允许你在组件被完全销毁之前执行一些操作。这和Vue 2中的beforeDestroy
生命周期钩子类似。
3.1 基本用法
<template>
<div>
<p>这个组件即将被卸载。</p>
</div>
</template>
<script setup>
import { onBeforeUnmount } from 'vue';
onBeforeUnmount(() => {
console.log('组件即将卸载!');
// 在这里可以执行一些卸载前的操作,例如保存数据,显示提示信息等。
});
</script>
3.2 应用场景
- 保存临时数据: 在组件卸载前,可以将一些临时数据保存到本地存储或者发送到服务器。
- 显示卸载提示: 可以在组件卸载前显示一个提示信息,告诉用户组件即将被卸载。
- 取消动画: 如果在组件卸载时正在播放动画,可以在
onBeforeUnmount
中停止动画,避免出现异常。
第四部分: VueUse中的妙用
VueUse本身就提供了一些基于onMounted
和onUnmounted
的实用hooks,比如:
useEventListener
: 用于监听DOM事件,并在组件卸载时自动解绑事件监听器。 避免手动调用addEventListener
和removeEventListener
,减少代码量,提高代码可维护性。
import { useEventListener } from '@vueuse/core';
import { ref, onMounted } from 'vue';
export default {
setup() {
const element = ref(null);
const x = ref(0);
const y = ref(0);
useEventListener(element, 'mousemove', (event) => {
x.value = event.clientX;
y.value = event.clientY;
});
onMounted(() => {
element.value = document.getElementById('my-element');
});
return { x, y, element };
},
template: `
<div id="my-element" style="width: 200px; height: 200px; background-color: lightblue;">
鼠标位置:X = {{ x }}, Y = {{ y }}
</div>
`
};
这个例子中,useEventListener
会自动监听my-element
的mousemove事件,并在组件卸载时自动移除监听器,无需手动管理。
第五部分:总结与思考
咱们今天聊了VueUse里onMounted
和onUnmounted
这两个hooks的实现原理和应用场景。 总结一下:
Hook | 作用 |
---|---|
onMounted |
在组件挂载到DOM后执行。可以用来获取DOM元素、发起网络请求、初始化第三方库等。 |
onUnmounted |
在组件从DOM中卸载之前执行。可以用来清理定时器、解绑事件监听器、取消网络请求、释放资源等,防止内存泄漏。 |
onBeforeUnmount |
在组件卸载之前执行,允许你在组件被完全销毁之前执行一些操作,例如保存临时数据,显示提示信息等。 |
理解这些hooks的实现原理,能让你更好地掌握Vue的生命周期,写出更高效、更健壮的Vue应用。
最后,留个小作业:
- 尝试自己实现一个简化版的
useEventListener
hook。 - 思考一下,如何在
onUnmounted
中优雅地取消Promise
请求?(提示:可以使用AbortController
)
希望今天的分享对大家有所帮助! 咱们下期再见!