钩子函数是什么意思_聊聊 beforeUnmount/unMounted 生命周期钩子函数

Hello,各位小伙伴,接下来的一段时间里,我会把我的课程《Vue.js 3.0 核心源码解析》中问题的答案陆续在我的公众号发布,由于课程的问题大多数都是开放性的问题,所以我的答案也不一定是标准的,仅供你参考喔。

本期的问题:如果你想在路由组件切换的时候,取消组件正在发送的异步 Ajax 请求,那你应该在哪个生命周期写这个逻辑呢?

这个问题回答起来其实很简单,因为在路由组件切换的时候,被切换的组件会执行 beforeUnmountunMounted 钩子函数,所以在这俩生命周期钩子函数内部去执行取消异步 Ajax 请求的逻辑都可以。

如果我是面试官,问了这道题,你仅仅知道上述答案是不够的,因为我还会继续问:beforeUnmountunMounted 有什么区别,我们通常有哪些场景需要在 beforeUnmount 或者是 unMounted 里去写逻辑,以及为什么要这么做。

这种刨根问底的手法面试官在面试中经常会用到,目的在考察候选人对知识的掌握程度,所以仅仅靠背答案是不靠谱的,你需要深入了解这些知识点。

那么接下来,我们就来一一解答这几个问题,希望对你有所启发和帮助。

beforeUnmount 和 unMounted 的区别

先看一张图回顾一下 Vue.js 3.0 组件的生命周期:

1091f2f1ece5b05acf7c11b882d56726.png

其中 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 操作,数据更新等
    }
  }
}

注意这里不能用匿名函数,因为 addEventListenerremoveEventListener 监听的事件函数需要指向同一个函数指针。

  • 第三方库

还有很多时候,我们会依赖一些第三方库辅助开发,而这些第三方库往往都会暴露一些 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 的逻辑,我们定义了 useScrollhook 函数,在这里我们仍然可以使用 Vue.js 提供的生命周期函数。

由于 BetterScroll 的初始化依赖容器的 DOM,所以我们在 onMounted 内部执行它的初始化逻辑。相应的,这次我们在 onUnmounted 内部执行 BS 实例的 destory 方法,这样就避免了组件销毁后 BS 实例内部带来的内存泄漏。

总结

在大多数情况下,我们使用 beforeUnmount 或者 unMounted 钩子函数都可以执行一些清理逻辑,至于用什么在于你的清理逻辑中有没有依赖 DOM,如果不依赖那么两者皆可。

此外,有些时候我们清理内存也并不一定依赖这俩个生命周期,你需要培养相关的意识,在合适的时机去做合适的事情。

我出这个题主要是希望你能做到以下两点:

  1. 从源码层面探索,了解 beforeUnmountunMounted 的差异。

  2. 从应用层面了解 beforeUnmountunMounted 应用场景,并学以致用。

要记住,分析和思考的过程远比答案重要。


版权声明:本文为weixin_39550940原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。