Hello,各位小伙伴,接下来的一段时间里,我会把我的课程《Vue.js 3.0 核心源码解析》中问题的答案陆续在我的公众号发布,由于课程的问题大多数都是开放性的问题,所以我的答案也不一定是标准的,仅供你参考喔。
本期的问题:如果你想在路由组件切换的时候,取消组件正在发送的异步 Ajax 请求,那你应该在哪个生命周期写这个逻辑呢?
这个问题回答起来其实很简单,因为在路由组件切换的时候,被切换的组件会执行 beforeUnmount
和 unMounted
钩子函数,所以在这俩生命周期钩子函数内部去执行取消异步 Ajax 请求的逻辑都可以。
如果我是面试官,问了这道题,你仅仅知道上述答案是不够的,因为我还会继续问:beforeUnmount
和 unMounted
有什么区别,我们通常有哪些场景需要在 beforeUnmount
或者是 unMounted 里去写逻辑,以及为什么要这么做。
这种刨根问底的手法面试官在面试中经常会用到,目的在考察候选人对知识的掌握程度,所以仅仅靠背答案是不靠谱的,你需要深入了解这些知识点。
那么接下来,我们就来一一解答这几个问题,希望对你有所启发和帮助。
beforeUnmount 和 unMounted 的区别
先看一张图回顾一下 Vue.js 3.0 组件的生命周期:
其中 beforeUnmount
发生在组件 unmount
之前,unMounted
发生在组件 unmount
之后,而 unmount
函数做的事情,就是执行组件自身的一些清理逻辑、递归销毁子组件,进而把组件下面所有的 DOM 也全部移除了。
因此,当我们执行的 beforeUnmount
的时候,还是可以访问组件内部的 DOM 的,如果你的代码逻辑依赖 DOM,那么就必须在 beforeUnmount
钩子函数中执行。
此外,Vue.js 只能在 unmount
函数中做一些组件自身的内存清理,而对于用户的一些自定义操作所占用的内存,是不会清理的。
beforeUnmount 和 unMounted 的常见应用场景
因此,我们通常会利用 beforeUnmount
或者是 unMounted
钩子函数主动执行一些清理操作,下面我列几个常见的应用场景:
- 定时器
假设你有一个计数组件,每秒加一,最简单的实现就是定义一个定时器,如下:
export default {
setup() {
let timer
const count = ref(0)
onMounted(()=>{
timer = setInterval(()=>{
count.value++
}, 1000)
})
return {
count
}
}
}
我们在 onMounted
钩子函数里用 setInterval
函数创建了一个定时器 timer
,如果这个组件被销毁了,定时器是不会主动销毁的,也就造成了内存泄漏。
因此需要你在 beforeUnmount
或者 unMounted
钩子函数中主动清理定时器,如下:
export default {
setup() {
let timer
const count = ref(0)
onMounted(()=>{
timer = setInterval(() => {
count.value++
}, 1000)
})
onBeforeUnmount(()=> {
clearInterval(timer)
})
return {
count
}
}
}
- 全局注册事件
有些时候,我们需要在组件中监听某些全局事件,如下:
export default {
setup() {
onMounted(()=>{
window.addEventListener('resize', onResize)
})
function onResize() {
// 做一些 DOM 操作,数据更新等
}
}
}
我们在 onMounted
钩子函数里监听了全局的 resize
事件,当窗口大小改变的时候,就会执行事件函数 onResize
,如果这个组件被销毁了,这个全局事件是不会主动解绑的。
因此需要你在 beforeUnmount
或者 unMounted
钩子函数中主动解绑事件,如下:
export default {
setup() {
onMounted(()=>{
window.addEventListener('resize', onResize)
})
onBeforeUnmount(()=> {
window.removeEventListener('resize', onResize)
})
function onResize() {
// 做一些 DOM 操作,数据更新等
}
}
}
注意这里不能用匿名函数,因为 addEventListener
和 removeEventListener
监听的事件函数需要指向同一个函数指针。
- 第三方库
还有很多时候,我们会依赖一些第三方库辅助开发,而这些第三方库往往都会暴露一些 API 来做一些库内部的清理操作。
比如我们在移动端常用的 BetterScroll,就提供了清理的 API,我们可以这么使用它:
export default function useScroll(wrapperRef, options) {
const scroll = ref(null)
onMounted(() => {
scroll.value = new BScroll(wrapperRef.value, options)
})
onUnmounted(() => {
scroll.value.destroy()
})
return scroll
}
为了独立 BetterScroll 的逻辑,我们定义了 useScroll
的 hook
函数,在这里我们仍然可以使用 Vue.js 提供的生命周期函数。
由于 BetterScroll 的初始化依赖容器的 DOM,所以我们在 onMounted
内部执行它的初始化逻辑。相应的,这次我们在 onUnmounted
内部执行 BS
实例的 destory
方法,这样就避免了组件销毁后 BS
实例内部带来的内存泄漏。
总结
在大多数情况下,我们使用 beforeUnmount
或者 unMounted
钩子函数都可以执行一些清理逻辑,至于用什么在于你的清理逻辑中有没有依赖 DOM,如果不依赖那么两者皆可。
此外,有些时候我们清理内存也并不一定依赖这俩个生命周期,你需要培养相关的意识,在合适的时机去做合适的事情。
我出这个题主要是希望你能做到以下两点:
从源码层面探索,了解
beforeUnmount
和unMounted
的差异。从应用层面了解
beforeUnmount
和unMounted
应用场景,并学以致用。
要记住,分析和思考的过程远比答案重要。