一、组合式API带来的新思考

自从Vue3推出组合式API后,整个开发模式都发生了翻天覆地的变化。以前在选项式API中,我们需要把代码分散到data、methods、created等不同选项中,现在可以像搭积木一样,把相关逻辑组织在一起了。

举个生活中的例子,选项式API就像把衣服、裤子、袜子分别放在不同抽屉里,而组合式API则是把一套完整的搭配放在一起。这种改变让代码的可维护性大大提高,特别是对于复杂组件来说。

二、生命周期钩子的华丽转身

在组合式API中,生命周期钩子也有了新的面貌。它们不再是配置项中的方法,而是变成了可以直接调用的函数。让我们看看这些钩子的新模样:

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

// 组件挂载完成后执行
onMounted(() => {
  console.log('组件已经挂载')
  // 这里适合做DOM操作、数据请求等初始化工作
})

// 组件更新后执行
onUpdated(() => {
  console.log('组件已经更新')
  // 可以在这里获取更新后的DOM状态
})

// 组件卸载前执行
onUnmounted(() => {
  console.log('组件即将卸载')
  // 清理定时器、取消事件监听等收尾工作
})
</script>

这些钩子函数使用起来非常直观,就像在跟组件对话一样:"当挂载完成后,请执行这段代码"。

三、常用生命周期钩子详解

1. onBeforeMount 和 onMounted

这对兄弟钩子分别代表组件挂载前后的关键时刻。onBeforeMount在挂载开始前调用,此时模板已经编译完成,但还没有替换DOM元素。onMounted则是组件已经挂载到DOM后的回调。

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

onBeforeMount(() => {
  console.log('准备挂载组件')
  // 此时还无法访问到DOM元素
})

onMounted(async () => {
  console.log('组件挂载完成')
  // 现在可以安全地访问DOM了
  const element = document.getElementById('my-element')
  
  // 适合在这里发起数据请求
  const data = await fetchData()
})
</script>

2. onBeforeUpdate 和 onUpdated

当组件响应式状态变化导致需要更新DOM时,这对钩子就会派上用场。onBeforeUpdate在DOM更新前调用,onUpdated则在DOM更新完成后触发。

<script setup lang="ts">
import { ref, onBeforeUpdate, onUpdated } from 'vue'

const count = ref(0)

onBeforeUpdate(() => {
  console.log('数据变化了,准备更新DOM')
  // 可以在这里获取更新前的DOM状态
})

onUpdated(() => {
  console.log('DOM更新完成')
  // 现在可以获取更新后的DOM状态
})
</script>

3. onBeforeUnmount 和 onUnmounted

这对钩子是组件的"临终关怀",在组件卸载前后执行。特别适合做清理工作,比如取消定时器、断开事件监听等。

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

const timer = setInterval(() => {
  console.log('定时器运行中...')
}, 1000)

onBeforeUnmount(() => {
  console.log('组件即将卸载')
})

onUnmounted(() => {
  console.log('组件已卸载')
  clearInterval(timer) // 清理定时器
})
</script>

四、特殊场景下的生命周期应用

1. 异步组件的加载状态

当使用异步组件时,我们可以利用onErrorCaptured来处理加载失败的情况:

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

const error = ref(null)

onErrorCaptured((err) => {
  error.value = err
  // 可以在这里记录错误或显示错误界面
  return false // 阻止错误继续向上传播
})
</script>

<template>
  <div v-if="error">加载出错: {{ error.message }}</div>
  <Suspense v-else>
    <!-- 异步组件内容 -->
  </Suspense>
</template>

2. 路由组件的特殊钩子

在使用Vue Router时,路由组件还有自己专属的导航守卫:

<script setup lang="ts">
import { onBeforeRouteUpdate, onBeforeRouteLeave } from 'vue-router'

// 当前路由改变,但该组件被复用时调用
onBeforeRouteUpdate(async (to, from) => {
  // 仅当id变化时才重新获取数据
  if (to.params.id !== from.params.id) {
    await fetchData(to.params.id)
  }
})

// 导航离开该组件的对应路由时调用
onBeforeRouteLeave((to, from) => {
  const answer = window.confirm('确定要离开吗?未保存的更改将会丢失')
  if (!answer) return false // 取消导航
})
</script>

五、组合式API的最佳实践

1. 逻辑复用与组合函数

组合式API最大的优势就是可以轻松提取可复用逻辑:

// useCounter.ts
import { ref, onMounted } from 'vue'

export function useCounter(initialValue = 0) {
  const count = ref(initialValue)
  
  function increment() {
    count.value++
  }
  
  // 可以在组合函数中使用生命周期
  onMounted(() => {
    console.log('计数器已初始化')
  })
  
  return { count, increment }
}

然后在组件中使用:

<script setup lang="ts">
import { useCounter } from './useCounter'

const { count, increment } = useCounter(10)
</script>

2. 生命周期执行顺序

了解多个组合函数中生命周期钩子的执行顺序很重要:

<script setup lang="ts">
import { onMounted } from 'vue'

// 按照注册顺序执行
onMounted(() => console.log('第一个'))
onMounted(() => console.log('第二个'))
</script>

输出顺序将是: 第一个 第二个

六、常见问题与解决方案

1. 生命周期钩子执行多次

有时候会发现生命周期钩子执行了多次,这通常是因为:

<script setup lang="ts">
import { onMounted } from 'vue'

// 如果在条件分支中注册钩子,可能导致意外行为
if (someCondition) {
  onMounted(() => {
    console.log('条件性挂载')
    // 当someCondition变化时,这个钩子可能会注册多次
  })
}
</script>

解决方案是确保钩子注册在顶层:

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

// 正确的做法
onMounted(() => {
  if (someCondition) {
    console.log('条件性逻辑')
  }
})

// 或者使用watch
watch(someCondition, (newVal) => {
  if (newVal) {
    // 执行相关逻辑
  }
})
</script>

2. 服务端渲染(SSR)兼容性

在SSR环境下,有些生命周期钩子不会执行:

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

// 在SSR中,只有onBeforeMount会执行
onBeforeMount(() => {
  console.log('这个会在SSR中执行')
})

onMounted(() => {
  console.log('这个只会在客户端执行')
})
</script>

七、总结与选择建议

组合式API的生命周期钩子提供了更灵活的代码组织方式。在实际开发中,我有几点建议:

  1. 尽量把相关逻辑和生命周期钩子放在一起,提高代码的内聚性
  2. 对于数据请求,onMounted是最常用的位置,但也要考虑SSR场景
  3. 清理工作一定要放在onUnmounted中,避免内存泄漏
  4. 路由组件的导航守卫可以处理特定于路由的逻辑
  5. 组合函数中也可以使用生命周期钩子,让逻辑更完整

记住,生命周期钩子就像组件的成长日记,记录着它从诞生到消亡的每一个重要时刻。用好它们,你的组件将会更加健壮和可维护。