MK
摩柯社区 - 一个极简的技术知识社区
AI 面试

Vue Composition API onMounted与onUnmounted的生命周期钩子使用

2024-03-243.4k 阅读

Vue Composition API 简介

Vue Composition API 是 Vue 3.0 中引入的一项重要特性,它为开发者提供了一种更加灵活和高效的方式来组织组件逻辑。与传统的 Vue 选项式 API 不同,Composition API 允许我们将相关的逻辑代码组合在一起,而不是按照选项的方式分散在组件中。这种方式使得代码更加可维护、可复用,尤其在处理复杂组件逻辑时优势明显。

生命周期钩子在 Vue 中的重要性

在 Vue 组件的整个生命周期中,会经历一系列的阶段,例如创建、挂载、更新和销毁等。生命周期钩子函数就是在这些不同阶段被调用的函数,开发者可以在这些钩子函数中执行特定的逻辑。比如在组件挂载后发起网络请求获取数据,在组件销毁前清理定时器等操作。这些操作对于组件的正确运行和资源管理至关重要。

onMounted 生命周期钩子

onMounted 的定义与触发时机

onMounted 是 Vue Composition API 提供的一个生命周期钩子函数,它在组件挂载到 DOM 后被调用。也就是说,当组件的模板已经被渲染成真实的 DOM 并插入到页面中时,onMounted 钩子函数会被触发。这为我们提供了一个在组件渲染完成后执行操作的时机。

代码示例 - onMounted 基本使用

下面通过一个简单的 Vue 组件示例来展示 onMounted 的基本使用方法:

<template>
  <div>
    <h1>onMounted 示例</h1>
    <p ref="message">{{ text }}</p>
  </div>
</template>

<script setup>
import { ref, onMounted } from 'vue';

const text = ref('初始文本');
const message = ref(null);

onMounted(() => {
  message.value = document.querySelector('p');
  console.log('组件已挂载,p 元素:', message.value);
  text.value = '挂载后修改的文本';
});
</script>

在上述代码中,我们首先定义了一个 text 响应式变量和一个 message 响应式变量(初始值为 null)。在 onMounted 钩子函数中,我们通过 document.querySelector('p') 获取到页面中的 <p> 元素,并将其赋值给 message.value,同时修改了 text 的值。这样,当组件挂载完成后,text 的值会更新,并且我们可以在控制台看到获取到的 <p> 元素信息。

onMounted 中操作 DOM 的注意事项

虽然在 onMounted 中可以直接操作 DOM,但需要注意以下几点:

  1. 避免过度依赖直接操作 DOM:Vue 本身是一个数据驱动的框架,尽量通过响应式数据来驱动 DOM 的变化。直接操作 DOM 可能会破坏 Vue 的数据绑定机制,导致后续维护困难。
  2. 兼容性问题:在一些非浏览器环境(如 SSR - Server - Side Rendering)中,直接操作 DOM 可能会导致错误。因为 SSR 环境下并没有真实的 DOM 存在。所以,如果代码需要在 SSR 环境中运行,需要对 DOM 操作进行特殊处理。

onUnmounted 生命周期钩子

onUnmounted 的定义与触发时机

onUnmounted 是 Vue Composition API 中另一个重要的生命周期钩子函数,它在组件从 DOM 中卸载后被调用。当组件被销毁,相关的 DOM 元素从页面中移除时,onUnmounted 钩子函数会被触发。这个钩子函数通常用于清理在组件挂载期间创建的资源,比如定时器、事件监听器等。

代码示例 - onUnmounted 基本使用

以下是一个展示 onUnmounted 基本用法的示例:

<template>
  <div>
    <h1>onUnmounted 示例</h1>
    <button @click="startTimer">开始定时器</button>
    <p v-if="timerText">{{ timerText }}</p>
  </div>
</template>

<script setup>
import { ref, onUnmounted } from 'vue';

const timerText = ref(null);
let timer = null;

const startTimer = () => {
  timer = setInterval(() => {
    if (!timerText.value) {
      timerText.value = '定时器开始';
    } else {
      timerText.value += ',时间流逝';
    }
  }, 1000);
};

onUnmounted(() => {
  if (timer) {
    clearInterval(timer);
    console.log('定时器已清理');
  }
});
</script>

在这个示例中,我们定义了一个 timerText 响应式变量用于显示定时器的相关信息,以及一个 timer 变量来存储定时器的引用。当点击按钮调用 startTimer 函数时,会启动一个每秒执行一次的定时器,不断更新 timerText 的值。在 onUnmounted 钩子函数中,我们检查 timer 是否存在,如果存在则清除定时器,这样可以避免在组件销毁后定时器仍然运行,从而导致内存泄漏等问题。

onUnmounted 清理资源的重要性

  1. 避免内存泄漏:如果在组件挂载期间创建了一些占用内存的资源(如定时器、事件监听器等),而在组件销毁时没有清理这些资源,会导致内存泄漏。随着组件的频繁创建和销毁,内存占用会不断增加,最终可能导致应用程序性能下降甚至崩溃。
  2. 防止遗留影响:未清理的事件监听器可能会在组件销毁后仍然对页面产生影响,比如继续触发一些不必要的操作,这会破坏应用程序的逻辑和用户体验。

onMounted 与 onUnmounted 的组合使用场景

网络请求与资源清理

在很多实际应用中,我们会在组件挂载后发起网络请求获取数据,并且在组件销毁时取消未完成的请求,以避免无效的请求和资源浪费。以下是一个简单的示例,展示如何使用 axios 进行网络请求,并在组件销毁时取消请求:

<template>
  <div>
    <h1>网络请求示例</h1>
    <p v-if="data">{{ data }}</p>
  </div>
</template>

<script setup>
import { ref, onMounted, onUnmounted } from 'vue';
import axios from 'axios';

const data = ref(null);
let cancelTokenSource = axios.CancelToken.source();

onMounted(() => {
  axios.get('/api/data', { cancelToken: cancelTokenSource.token })
  .then(response => {
      data.value = response.data;
    })
  .catch(error => {
      if (axios.isCancel(error)) {
        console.log('请求已取消');
      } else {
        console.error('请求出错:', error);
      }
    });
});

onUnmounted(() => {
  cancelTokenSource.cancel('组件销毁,取消请求');
});
</script>

在上述代码中,我们在 onMounted 钩子函数中发起一个网络请求,并传入 cancelToken。在 onUnmounted 钩子函数中,调用 cancelTokenSource.cancel 方法取消请求。这样,当组件销毁时,未完成的网络请求会被取消,避免了不必要的资源消耗。

自定义指令与 DOM 清理

有时候我们会创建自定义指令,在组件挂载时对 DOM 元素进行一些特殊的操作,而在组件销毁时需要清理这些操作。例如,我们创建一个自定义指令来为元素添加一个点击时显示提示信息的功能:

<template>
  <div>
    <h1>自定义指令示例</h1>
    <button v-tip="'点击我有提示'">点击我</button>
  </div>
</template>

<script setup>
import { onMounted, onUnmounted } from 'vue';

const tipDirective = {
  mounted(el, binding) {
    const tip = document.createElement('span');
    tip.textContent = binding.value;
    tip.style.position = 'absolute';
    tip.style.left = '50%';
    tip.style.transform = 'translateX(-50%)';
    tip.style.backgroundColor = 'rgba(0, 0, 0, 0.7)';
    tip.style.color = 'white';
    tip.style.padding = '5px 10px';
    tip.style.borderRadius = '5px';
    tip.style.opacity = '0';
    tip.style.transition = 'opacity 0.3s ease';

    el.addEventListener('click', () => {
      tip.style.opacity = '1';
      setTimeout(() => {
        tip.style.opacity = '0';
      }, 2000);
    });

    document.body.appendChild(tip);
    el._tip = tip;
  },
  unmounted(el) {
    if (el._tip) {
      document.body.removeChild(el._tip);
      delete el._tip;
    }
  }
};

app.directive('tip', tipDirective);
</script>

在这个示例中,自定义指令 v - tipmounted 钩子函数中为按钮元素添加了点击显示提示信息的功能,并在 unmounted 钩子函数中清理了添加到页面中的提示元素,避免了组件销毁后 DOM 中遗留无用的元素。

在复杂组件中使用 onMounted 和 onUnmounted

多逻辑模块的组织

在复杂组件中,往往会涉及多个不同的逻辑模块,每个模块可能都有自己在组件挂载和销毁时需要执行的操作。使用 Vue Composition API 的 onMountedonUnmounted 可以将这些逻辑清晰地组织起来。例如,一个包含地图展示、数据加载和实时更新功能的组件:

<template>
  <div>
    <h1>复杂组件示例</h1>
    <div id="map" ref="mapRef"></div>
    <p v - for="item in data" :key="item.id">{{ item.name }}</p>
  </div>
</template>

<script setup>
import { ref, onMounted, onUnmounted } from 'vue';
import mapboxgl from'mapboxgl';
import axios from 'axios';

const mapRef = ref(null);
const data = ref([]);
let map = null;
let updateInterval = null;

// 地图相关逻辑
const initMap = () => {
  map = new mapboxgl.Map({
    container: mapRef.value,
    style: 'mapbox://styles/mapbox/streets - v11',
    center: [-74.5, 40],
    zoom: 9
  });
};

const cleanMap = () => {
  if (map) {
    map.remove();
    map = null;
  }
};

// 数据加载逻辑
const loadData = () => {
  axios.get('/api/data')
  .then(response => {
      data.value = response.data;
    })
  .catch(error => {
      console.error('数据加载出错:', error);
    });
};

// 实时更新逻辑
const startAutoUpdate = () => {
  updateInterval = setInterval(() => {
    loadData();
  }, 5000);
};

const stopAutoUpdate = () => {
  if (updateInterval) {
    clearInterval(updateInterval);
    updateInterval = null;
  }
};

onMounted(() => {
  initMap();
  loadData();
  startAutoUpdate();
});

onUnmounted(() => {
  cleanMap();
  stopAutoUpdate();
});
</script>

在这个示例中,我们将地图初始化、数据加载和实时更新功能分成了不同的逻辑模块,并在 onMountedonUnmounted 中分别调用相应的初始化和清理函数。这样使得代码结构更加清晰,易于维护和扩展。

逻辑复用与组合

Vue Composition API 的优势之一就是逻辑复用。我们可以将一些通用的挂载和销毁逻辑封装成函数,然后在不同的组件中复用。例如,我们有一个用于处理页面滚动监听的逻辑模块:

import { onMounted, onUnmounted } from 'vue';

const useScrollListener = () => {
  const handleScroll = () => {
    console.log('页面滚动了');
  };

  onMounted(() => {
    window.addEventListener('scroll', handleScroll);
  });

  onUnmounted(() => {
    window.removeEventListener('scroll', handleScroll);
  });
};

export default useScrollListener;

然后在组件中使用这个逻辑模块:

<template>
  <div>
    <h1>逻辑复用示例</h1>
  </div>
</template>

<script setup>
import useScrollListener from './useScrollListener.js';

useScrollListener();
</script>

通过这种方式,我们可以将页面滚动监听的逻辑复用在多个组件中,减少了代码的重复。

与传统选项式 API 生命周期钩子的对比

代码组织方式的差异

在传统的 Vue 选项式 API 中,生命周期钩子函数是在组件的 options 对象中定义的,例如:

<template>
  <div>
    <h1>选项式 API 生命周期示例</h1>
  </div>
</template>

<script>
export default {
  mounted() {
    console.log('组件已挂载(选项式 API)');
  },
  beforeUnmount() {
    console.log('组件即将卸载(选项式 API)');
  }
};
</script>

而在 Vue Composition API 中,onMountedonUnmounted 是通过导入并直接调用的方式使用,逻辑更加集中。例如:

<template>
  <div>
    <h1>Composition API 生命周期示例</h1>
  </div>
</template>

<script setup>
import { onMounted, onUnmounted } from 'vue';

onMounted(() => {
  console.log('组件已挂载(Composition API)');
});

onUnmounted(() => {
  console.log('组件即将卸载(Composition API)');
});
</script>

这种差异使得在处理复杂组件逻辑时,Composition API 能够更好地将相关逻辑组合在一起,而选项式 API 可能会因为逻辑分散在不同的选项中而变得难以维护。

逻辑复用的便利性

在选项式 API 中,逻辑复用通常通过 mixins 来实现,但 mixins 存在命名冲突和数据来源不清晰等问题。而在 Composition API 中,通过将相关逻辑封装成函数(如前面提到的 useScrollListener),可以更加方便地在不同组件中复用,并且不会产生命名冲突等问题。

对 TypeScript 的支持

Vue Composition API 对 TypeScript 的支持更加友好。在使用选项式 API 时,TypeScript 的类型推导可能会因为选项的分散而变得复杂。而在 Composition API 中,由于逻辑更加集中,TypeScript 可以更准确地进行类型推导,使得代码的类型安全性更高。例如:

<template>
  <div>
    <h1>TypeScript 与 Composition API 示例</h1>
  </div>
</template>

<script setup lang="ts">
import { ref, onMounted, onUnmounted } from 'vue';

const count = ref<number>(0);

onMounted(() => {
  count.value++;
  console.log('组件已挂载,count:', count.value);
});

onUnmounted(() => {
  console.log('组件即将卸载,count:', count.value);
});
</script>

在这个示例中,TypeScript 可以很清晰地推断出 count 的类型为 number,并且对 onMountedonUnmounted 中的操作进行准确的类型检查。

总结

Vue Composition API 的 onMountedonUnmounted 生命周期钩子为开发者提供了一种更加灵活、高效的方式来管理组件的挂载和卸载逻辑。通过清晰的代码示例和深入的原理讲解,我们了解了它们的触发时机、使用方法以及在实际应用中的各种场景。与传统选项式 API 的生命周期钩子相比,Composition API 在代码组织、逻辑复用和对 TypeScript 的支持等方面都具有明显的优势。在实际项目开发中,合理运用 onMountedonUnmounted 可以提高代码的可维护性和复用性,打造出更加健壮和高效的前端应用程序。同时,我们也应该注意在使用过程中遵循最佳实践,避免因不当操作导致的内存泄漏、兼容性问题等。希望开发者们能够熟练掌握并运用这两个重要的生命周期钩子,为 Vue 应用开发带来更多的便利和创新。