一、组合式API带来的新变化

在Vue3中,最引人注目的变化莫过于组合式API(Composition API)的引入。这就像是从传统的"选项式"厨房升级到了现代化的"模块化"厨房,让我们可以更灵活地组织和复用代码逻辑。

想象一下,以前我们做菜(写组件)时,所有食材(数据)和厨具(方法)都分散在不同的抽屉(options)里。现在我们可以把相关的食材和厨具放在同一个料理台(setup函数)上,做菜效率自然就提高了。

// 技术栈:TypeScript + Vue3
import { ref, onMounted } from 'vue'

export default {
  setup() {
    // 定义响应式数据(相当于data选项)
    const count = ref(0)
    
    // 定义方法(相当于methods选项)
    const increment = () => {
      count.value++
    }
    
    // 生命周期钩子(相当于mounted选项)
    onMounted(() => {
      console.log('组件挂载完成啦!')
    })
    
    // 暴露给模板使用
    return {
      count,
      increment
    }
  }
}

二、生命周期钩子的映射关系

Vue3的生命周期钩子在使用方式上有了很大变化,但核心思想没变。就像手机从按键式升级到触屏式,操作方式变了,但打电话、发短信的功能还在。

这里有个完整的映射表:

Vue2选项式API Vue3组合式API
beforeCreate 不需要(setup替代)
created 不需要(setup替代)
beforeMount onBeforeMount
mounted onMounted
beforeUpdate onBeforeUpdate
updated onUpdated
beforeUnmount onBeforeUnmount
unmounted onUnmounted
activated onActivated
deactivated onDeactivated
errorCaptured onErrorCaptured

让我们看个完整示例:

import {
  onBeforeMount,
  onMounted,
  onBeforeUpdate,
  onUpdated,
  onBeforeUnmount,
  onUnmounted
} from 'vue'

export default {
  setup() {
    // 组件即将挂载
    onBeforeMount(() => {
      console.log('准备上车啦!')
    })
    
    // 组件已挂载
    onMounted(() => {
      console.log('已经坐稳了,可以发车了!')
    })
    
    // 组件即将更新
    onBeforeUpdate(() => {
      console.log('注意!数据要变啦!')
    })
    
    // 组件已更新
    onUpdated(() => {
      console.log('数据更新完成,界面已刷新')
    })
    
    // 组件即将卸载
    onBeforeUnmount(() => {
      console.log('准备下车了,别忘带东西!')
    })
    
    // 组件已卸载
    onUnmounted(() => {
      console.log('拜拜了您嘞~')
    })
  }
}

三、执行时机与使用技巧

理解生命周期钩子的执行时机非常重要,这就像知道火车时刻表一样,错过了就得等下一班。下面我们通过一个实际场景来演示:

import { ref, onMounted, onUpdated } from 'vue'

export default {
  setup() {
    const userList = ref<string[]>([])
    const loading = ref(false)
    
    // 模拟异步获取用户数据
    const fetchUsers = async () => {
      loading.value = true
      try {
        // 这里假设调用API获取数据
        const response = await fetch('/api/users')
        userList.value = await response.json()
      } finally {
        loading.value = false
      }
    }
    
    // 组件挂载后立即获取数据
    onMounted(fetchUsers)
    
    // 当userList更新时执行某些操作
    onUpdated(() => {
      if (userList.value.length > 0) {
        console.log('用户列表已更新,当前用户数:', userList.value.length)
      }
    })
    
    return {
      userList,
      loading
    }
  }
}

在这个例子中,我们需要注意几个关键点:

  1. onMounted是最常用的钩子,适合执行初始化操作,比如获取数据、设置定时器等
  2. onUpdated会在每次数据变化导致DOM更新后触发,但要小心避免无限循环
  3. 异步操作最好配合refreactive使用,确保响应式更新

四、常见问题与最佳实践

在实际开发中,我们经常会遇到一些生命周期相关的问题。下面列举几个典型场景:

场景一:在setup中访问DOM元素

import { ref, onMounted } from 'vue'

export default {
  setup() {
    const root = ref<HTMLElement | null>(null)
    
    onMounted(() => {
      // 只有在mounted后才能访问DOM元素
      console.log('根元素宽度:', root.value?.offsetWidth)
    })
    
    return {
      root
    }
  }
}

场景二:清理副作用

import { onMounted, onUnmounted } from 'vue'

export default {
  setup() {
    let timer: number
    
    onMounted(() => {
      timer = setInterval(() => {
        console.log('定时器运行中...')
      }, 1000)
    })
    
    onUnmounted(() => {
      // 组件卸载时清除定时器
      clearInterval(timer)
    })
  }
}

场景三:多个同类钩子的执行顺序

import { onMounted } from 'vue'

export default {
  setup() {
    // 多个同类型钩子会按照注册顺序执行
    onMounted(() => {
      console.log('第一个mounted钩子')
    })
    
    onMounted(() => {
      console.log('第二个mounted钩子')
    })
  }
}

最佳实践建议:

  1. 将相关的生命周期逻辑组织在一起
  2. 清理工作一定要在onUnmounted中进行
  3. 避免在onUpdated中修改状态,可能导致无限循环
  4. 对于复杂组件,可以考虑使用自定义hook来封装生命周期逻辑

五、与Vue2的对比与迁移

对于从Vue2迁移过来的开发者,理解新旧生命周期的对应关系很重要。Vue3中:

  1. beforeCreatecreatedsetup函数替代
  2. beforeDestroydestroyed更名为beforeUnmountunmounted
  3. 所有钩子都需要显式导入并使用

迁移示例:

// Vue2写法
export default {
  data() {
    return {
      count: 0
    }
  },
  created() {
    console.log('组件创建完成')
  },
  mounted() {
    console.log('组件挂载完成')
  },
  beforeDestroy() {
    console.log('组件即将销毁')
  }
}

// Vue3等效写法
import { ref, onMounted, onBeforeUnmount } from 'vue'

export default {
  setup() {
    const count = ref(0)
    
    console.log('组件创建完成') // 直接写在setup中相当于created
    
    onMounted(() => {
      console.log('组件挂载完成')
    })
    
    onBeforeUnmount(() => {
      console.log('组件即将销毁')
    })
    
    return {
      count
    }
  }
}

六、高级应用场景

对于更复杂的场景,比如需要在路由变化时重用组件,我们可以结合onActivatedonDeactivated来实现:

import { onActivated, onDeactivated } from 'vue'

export default {
  setup() {
    // 当使用<keep-alive>缓存组件时
    onActivated(() => {
      console.log('组件被激活')
      // 可以在这里刷新数据
    })
    
    onDeactivated(() => {
      console.log('组件被停用')
      // 可以在这里暂停视频播放等
    })
  }
}

另一个常见场景是错误处理:

import { onErrorCaptured } from 'vue'

export default {
  setup() {
    onErrorCaptured((err, instance, info) => {
      console.error('捕获到子组件错误:', err)
      // 可以在这里上报错误
      return false // 阻止错误继续向上传播
    })
  }
}

七、总结与建议

通过本文的讲解,相信大家对Vue3组合式API中的生命周期有了更深入的理解。总结几个关键点:

  1. 组合式API让生命周期逻辑更集中,便于维护
  2. 所有钩子都需要从vue中显式导入
  3. setup函数替代了beforeCreatecreated
  4. 清理工作务必在onUnmounted中进行
  5. 对于缓存组件,可以使用onActivatedonDeactivated

在实际项目中,建议:

  • 将复杂的生命周期逻辑封装成自定义hook
  • 注意内存泄漏问题,及时清理定时器、事件监听等
  • 合理使用onUpdated,避免性能问题
  • 结合TypeScript可以获得更好的类型提示