Vue.js生命周期钩子

每个 Vue 组件实例在创建时都会经历一系列的初始化步骤,这个过程被称为组件的生命周期。Vue 在生命周期的各个关键时刻提供了钩子函数,允许我们在特定阶段执行自定义逻辑。

生命周期概述

Vue 组件的生命周期可以分为四个主要阶段:

1. 创建阶段 (Creation)

初始化数据观测、事件、计算属性等

2. 挂载阶段 (Mounting)

将模板编译成渲染函数,创建虚拟DOM,挂载到真实DOM

3. 更新阶段 (Updating)

响应式数据变化时,重新渲染组件

4. 卸载阶段 (Destruction)

组件实例被销毁,清理事件监听器、定时器等

核心概念

生命周期钩子允许我们在组件生命周期的特定时刻执行代码。理解这些钩子的执行顺序对于编写正确的组件逻辑至关重要。

生命周期图示

初始化事件和生命周期
beforeCreate
初始化注入和响应性
created
编译模板/渲染函数
beforeMount
创建虚拟DOM并挂载
mounted
数据变化时重新渲染
beforeUpdate
更新虚拟DOM并应用
updated
组件卸载前
beforeUnmount
卸载组件和清理
unmounted

生命周期钩子对比

Vue 3 提供了两种API风格:Options API 和 Composition API。以下是两种API中生命周期钩子的对应关系:

生命周期阶段 Options API Composition API 执行时机
创建前 beforeCreate setup() 替代 实例初始化后,数据观测之前
创建后 created setup() 替代 实例创建完成,数据观测已建立
挂载前 beforeMount onBeforeMount 挂载开始之前,render 函数首次调用
挂载后 mounted onMounted 实例挂载到 DOM 之后
更新前 beforeUpdate onBeforeUpdate 数据变化,虚拟 DOM 重新渲染之前
更新后 updated onUpdated 数据变化,虚拟 DOM 重新渲染之后
卸载前 beforeUnmount (Vue 3)
beforeDestroy (Vue 2)
onBeforeUnmount 实例销毁之前
卸载后 unmounted (Vue 3)
destroyed (Vue 2)
onUnmounted 实例销毁之后
激活 activated onActivated 被 keep-alive 缓存的组件激活时
停用 deactivated onDeactivated 被 keep-alive 缓存的组件停用时

Composition API 生命周期钩子

在 Composition API 中,生命周期钩子作为函数从 vue 包中导入,并在 setup() 函数中调用。

基本语法

Composition API生命周期钩子导入和使用

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

export default {
  setup() {
    // 生命周期钩子需要在 setup() 中同步调用
    onBeforeMount(() => {
      console.log('组件挂载前')
    })

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

    onBeforeUpdate(() => {
      console.log('组件更新前')
    })

    onUpdated(() => {
      console.log('组件已更新')
    })

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

    onUnmounted(() => {
      console.log('组件已卸载')
    })

    onActivated(() => {
      console.log('组件被激活')
    })

    onDeactivated(() => {
      console.log('组件被停用')
    })

    onErrorCaptured((err, instance, info) => {
      console.error('捕获到错误:', err)
      // 返回 false 阻止错误继续向上传播
      return false
    })

    return {
      // 返回模板中需要使用的数据和方法
    }
  }
}
                        

生命周期钩子详解

1. setup() 替代 beforeCreate 和 created

在 Composition API 中,beforeCreatecreated 的生命周期被 setup() 函数替代。setup() 在组件创建之前执行,是 Composition API 的入口点。

示例setup() 函数的使用

import { ref } from 'vue'

export default {
  setup(props, context) {
    // 这个阶段相当于 beforeCreate 和 created
    console.log('setup() 执行 - 组件初始化')

    // 在这里定义响应式数据、计算属性、方法等
    const count = ref(0)
    const message = ref('Hello Vue')

    // 访问 props
    console.log('props:', props)

    // 访问上下文 (attrs, slots, emit)
    console.log('context:', context)

    // 返回模板中需要使用的数据和方法
    return {
      count,
      message
    }
  }
}
                        

2. onBeforeMount 和 onMounted

onBeforeMount 在组件挂载到 DOM 之前调用,此时模板已编译但尚未渲染为真实 DOM。onMounted 在组件挂载到 DOM 之后调用,可以访问 DOM 元素。

示例挂载阶段的生命周期

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

export default {
  setup() {
    const count = ref(0)
    const containerRef = ref(null)

    onBeforeMount(() => {
      console.log('onBeforeMount: 组件即将挂载')
      // 此时无法访问 DOM 元素
      console.log('containerRef:', containerRef.value) // null
    })

    onMounted(() => {
      console.log('onMounted: 组件已挂载')
      // 此时可以访问 DOM 元素
      console.log('containerRef:', containerRef.value) // DOM 元素

      // 常见的 mounted 使用场景:
      // 1. 初始化第三方库
      // 2. 发送初始数据请求
      // 3. 添加事件监听器
      // 4. 操作 DOM 元素
    })

    return {
      count,
      containerRef
    }
  }
}
                        
onMounted 常见用途
  • 发送 AJAX 请求获取初始数据
  • 初始化第三方库(如图表库、地图等)
  • 操作 DOM 元素(如表单自动聚焦)
  • 添加事件监听器(如 window.resize)
  • 设置定时器或动画

3. onBeforeUpdate 和 onUpdated

当响应式数据变化导致组件需要重新渲染时,会触发更新阶段的生命周期钩子。

示例更新阶段的生命周期

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

export default {
  setup() {
    const count = ref(0)
    const message = ref('')

    onBeforeUpdate(() => {
      console.log('onBeforeUpdate: 组件即将更新')
      console.log('当前 count:', count.value)
      // 此时 DOM 还未更新
    })

    onUpdated(() => {
      console.log('onUpdated: 组件已更新')
      console.log('更新后的 count:', count.value)
      // 此时 DOM 已更新

      // 注意:避免在 updated 中修改状态,可能导致无限循环
    })

    const increment = () => {
      count.value++
    }

    return {
      count,
      message,
      increment
    }
  }
}
                        
onUpdated 注意事项

onUpdated 会在每次组件更新后调用,包括子组件的更新。要避免在这里修改状态,否则可能导致无限更新循环。

4. onBeforeUnmount 和 onUnmounted

当组件被销毁时,会触发卸载阶段的生命周期钩子。

示例卸载阶段的生命周期

import { ref, onBeforeUnmount, onUnmounted } from 'vue'

export default {
  setup() {
    const timerId = ref(null)

    // 在 mounted 中设置定时器
    onMounted(() => {
      timerId.value = setInterval(() => {
        console.log('定时器执行')
      }, 1000)
    })

    onBeforeUnmount(() => {
      console.log('onBeforeUnmount: 组件即将卸载')
      // 此时组件实例仍完全可用
    })

    onUnmounted(() => {
      console.log('onUnmounted: 组件已卸载')
      // 清理定时器,避免内存泄漏
      if (timerId.value) {
        clearInterval(timerId.value)
        timerId.value = null
      }

      // 其他清理工作:
      // 1. 移除事件监听器
      // 2. 取消网络请求
      // 3. 清理第三方库实例
      // 4. 取消订阅
    })

    return {}
  }
}
                        

keep-alive 相关生命周期

当组件被 <keep-alive> 包裹时,会有两个额外的生命周期钩子:

示例keep-alive 生命周期

import { onActivated, onDeactivated } from 'vue'

export default {
  setup() {
    const activeCount = ref(0)

    onActivated(() => {
      console.log('onActivated: 组件被激活')
      activeCount.value++

      // 常见用途:
      // 1. 恢复定时器
      // 2. 重新获取数据
      // 3. 重新计算位置
    })

    onDeactivated(() => {
      console.log('onDeactivated: 组件被停用')

      // 常见用途:
      // 1. 暂停定时器
      // 2. 保存组件状态
      // 3. 取消未完成的请求
    })

    return {
      activeCount
    }
  }
}

// 父组件中使用 keep-alive
// <keep-alive>
//   <my-component v-if="show" />
// </keep-alive>
                        

错误处理钩子

onErrorCaptured 用于捕获来自子组件的错误。

示例错误捕获

import { onErrorCaptured } from 'vue'

export default {
  setup() {
    onErrorCaptured((err, instance, info) => {
      // err: 错误对象
      // instance: 触发错误的组件实例
      // info: 错误信息字符串,如 'render function', 'event handler'

      console.error('捕获到错误:', err)
      console.log('组件实例:', instance)
      console.log('错误信息:', info)

      // 可以在此处发送错误到错误监控服务
      sendErrorToMonitoringService(err)

      // 返回 false 阻止错误继续向上传播
      return false
    })

    return {}
  }
}
                        

Options API 生命周期钩子

对于仍然使用 Options API 的项目,以下是生命周期钩子的使用方式:

Options API完整的生命周期示例

export default {
  data() {
    return {
      count: 0,
      message: 'Hello Vue'
    }
  },

  beforeCreate() {
    console.log('beforeCreate: 实例初始化后,数据观测之前')
    // 此时 this 还不能访问 data、computed、methods 等
    console.log('this.count:', this.count) // undefined
  },

  created() {
    console.log('created: 实例创建完成')
    // 此时可以访问 data、computed、methods 等
    console.log('this.count:', this.count) // 0

    // 常见用途:发送初始数据请求
    this.fetchInitialData()
  },

  beforeMount() {
    console.log('beforeMount: 挂载之前')
    // 此时模板已编译,但尚未挂载到 DOM
  },

  mounted() {
    console.log('mounted: 实例已挂载')
    // 此时可以访问 DOM 元素
    console.log('this.$el:', this.$el)
  },

  beforeUpdate() {
    console.log('beforeUpdate: 数据更新,DOM 重新渲染之前')
    console.log('当前 count:', this.count)
  },

  updated() {
    console.log('updated: 数据更新,DOM 重新渲染之后')
    console.log('更新后的 count:', this.count)
  },

  beforeUnmount() { // Vue 3
  // beforeDestroy() { // Vue 2
    console.log('beforeUnmount: 实例销毁之前')
    // 此时组件实例仍完全可用
  },

  unmounted() { // Vue 3
  // destroyed() { // Vue 2
    console.log('unmounted: 实例销毁之后')
    // 执行清理工作
  },

  activated() {
    console.log('activated: 被 keep-alive 缓存的组件激活时')
  },

  deactivated() {
    console.log('deactivated: 被 keep-alive 缓存的组件停用时')
  },

  errorCaptured(err, instance, info) {
    console.error('捕获到错误:', err)
    return false // 阻止错误继续向上传播
  },

  methods: {
    fetchInitialData() {
      // 发送初始数据请求
    }
  }
}
                        

实际应用示例

示例1:数据获取和清理

综合示例使用生命周期管理数据请求

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

export default {
  setup() {
    const posts = ref([])
    const isLoading = ref(false)
    const error = ref(null)
    let abortController = null

    const fetchPosts = async () => {
      isLoading.value = true
      error.value = null

      // 创建 AbortController 用于取消请求
      abortController = new AbortController()

      try {
        const response = await fetch('https://api.example.com/posts', {
          signal: abortController.signal
        })
        if (!response.ok) throw new Error('网络响应错误')
        posts.value = await response.json()
      } catch (err) {
        if (err.name !== 'AbortError') {
          error.value = err.message
        }
      } finally {
        isLoading.value = false
      }
    }

    // 组件挂载时获取数据
    onMounted(() => {
      fetchPosts()
    })

    // 组件卸载时取消请求
    onUnmounted(() => {
      if (abortController) {
        abortController.abort()
      }
    })

    return {
      posts,
      isLoading,
      error
    }
  }
}
                        

示例2:事件监听器管理

综合示例使用生命周期管理事件监听器

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

export default {
  setup() {
    const windowWidth = ref(window.innerWidth)
    const mousePosition = ref({ x: 0, y: 0 })

    const handleResize = () => {
      windowWidth.value = window.innerWidth
    }

    const handleMouseMove = (event) => {
      mousePosition.value = {
        x: event.clientX,
        y: event.clientY
      }
    }

    // 组件挂载时添加事件监听器
    onMounted(() => {
      window.addEventListener('resize', handleResize)
      document.addEventListener('mousemove', handleMouseMove)

      // 初始化数据
      handleResize()
    })

    // 组件卸载时移除事件监听器
    onUnmounted(() => {
      window.removeEventListener('resize', handleResize)
      document.removeEventListener('mousemove', handleMouseMove)
    })

    return {
      windowWidth,
      mousePosition
    }
  }
}
                        

生命周期最佳实践

生命周期最佳实践
  • 数据初始化:在 createdsetup() 中初始化数据,而不是 beforeCreate
  • DOM 操作:在 mountedonMounted 中进行 DOM 操作
  • 数据请求:在 mountedonMounted 中发送初始数据请求
  • 清理工作:在 beforeUnmountonBeforeUnmount 中执行清理
  • 避免副作用:避免在 updatedonUpdated 中修改状态
  • 第三方库:在 mounted 中初始化第三方库,在 beforeUnmount 中销毁

生命周期时序演示

生命周期钩子执行顺序演示

点击按钮观察生命周期钩子的执行顺序:

等待操作...
当前状态:未挂载

常见问题

常见问题解答
1. Composition API 中的 beforeCreate 和 created 去哪了?

在 Composition API 中,setup() 函数替代了 beforeCreatecreated 钩子。setup() 在组件创建之前执行,可以在这里进行所有初始化工作。

2. 为什么在 updated 钩子中修改状态会导致无限循环?

updated 钩子会在组件更新后调用,如果在其中修改响应式数据,会触发新一轮的更新,导致无限循环。

3. 什么时候使用 keep-alive 相关的生命周期?

当组件被 <keep-alive> 包裹时,使用 activateddeactivated 钩子来管理组件的激活和停用状态,如恢复/暂停定时器、重新获取数据等。

4. 如何在父子组件中处理生命周期顺序?

Vue 组件的生命周期顺序是:父组件 beforeCreate → 父组件 created → 父组件 beforeMount → 子组件生命周期 → 父组件 mounted

总结

Vue.js 的生命周期钩子提供了在组件各个阶段执行代码的能力。理解每个钩子的执行时机和用途对于编写正确的 Vue 组件至关重要。

关键要点:

  • 创建阶段:初始化数据、事件、响应式系统
  • 挂载阶段:编译模板、创建虚拟DOM、挂载到真实DOM
  • 更新阶段:响应数据变化、重新渲染组件
  • 卸载阶段:清理资源、避免内存泄漏

无论使用 Options API 还是 Composition API,理解生命周期原理都能帮助你编写更健壮、更高效的 Vue 应用。

测试你的理解

思考题
  1. 在哪个生命周期钩子中可以安全地访问 DOM 元素?
  2. 为什么要在 beforeUnmountonBeforeUnmount 中清理定时器和事件监听器?
  3. setup() 函数对应 Options API 中的哪些生命周期钩子?
  4. 如何在组件激活时重新获取数据?
  5. 父子组件的生命周期执行顺序是怎样的?