Vue 3源码极客之:`Vue`的`VueUse`:其核心实现`onMounted`和`onUnmounted`的`hooks`。

各位前端的英雄好汉们,早上/下午/晚上好!我是今天的主讲人,咱们今天聊聊Vue 3源码里那些你可能没注意到的宝藏,特别是VueUse里onMountedonUnmounted这两个看似简单,实则蕴含着Vue生命周期精髓的hooks。别紧张,咱们不搞那些枯燥的理论,争取用最轻松幽默的方式,把这些东西给扒个底朝天。

开场白:VueUse是啥?为啥要聊它?

首先,咱们得搞清楚VueUse是个啥玩意儿。简单来说,它就是一个Vue组合式函数(Composition API)的工具库,里面包含了各种各样的实用hooks,比如咱们今天要讲的onMountedonUnmounted

为啥要聊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之外使用onMountedcurrentInstance可能为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里,我们用的是beforeDestroydestroyed选项,而在Vue 3里,我们用onBeforeUnmountonUnmounted这两个组合式函数。 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 的例子一样)

可以看到,onUnmountedonMounted的代码几乎一样,唯一的区别就是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>
  • 取消网络请求: 如果在组件挂载后发起了网络请求,在组件卸载时可以取消未完成的请求。(需要配合axioscancelToken等机制)
  • 释放资源: 如果组件使用了某些需要手动释放的资源,比如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本身就提供了一些基于onMountedonUnmounted的实用hooks,比如:

  • useEventListener: 用于监听DOM事件,并在组件卸载时自动解绑事件监听器。 避免手动调用addEventListenerremoveEventListener,减少代码量,提高代码可维护性。
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里onMountedonUnmounted这两个hooks的实现原理和应用场景。 总结一下:

Hook 作用
onMounted 在组件挂载到DOM后执行。可以用来获取DOM元素、发起网络请求、初始化第三方库等。
onUnmounted 在组件从DOM中卸载之前执行。可以用来清理定时器、解绑事件监听器、取消网络请求、释放资源等,防止内存泄漏。
onBeforeUnmount 在组件卸载之前执行,允许你在组件被完全销毁之前执行一些操作,例如保存临时数据,显示提示信息等。

理解这些hooks的实现原理,能让你更好地掌握Vue的生命周期,写出更高效、更健壮的Vue应用。

最后,留个小作业:

  1. 尝试自己实现一个简化版的useEventListener hook。
  2. 思考一下,如何在onUnmounted中优雅地取消Promise请求?(提示:可以使用AbortController

希望今天的分享对大家有所帮助! 咱们下期再见!

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注