第4章:响应式系统与生命周期

第11节:生命周期钩子详解

4.11.1 生命周期图示与阶段划分

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

初始化阶段 → 挂载阶段 → 更新阶段 → 卸载阶段

生命周期图示

+---------------------------+-------------------------+---------------------------+
|       初始化阶段          |       挂载阶段          |       更新阶段            |
+---------------------------+-------------------------+---------------------------+
|                           |                         |                           |
|  beforeCreate             |  beforeMount            |  beforeUpdate             |
|  created                  |  mounted                |  updated                  |
|                           |                         |                           |
+---------------------------+-------------------------+---------------------------+
                                           |
                                           ↓
                                   +---------------------------+
                                   |       卸载阶段            |
                                   +---------------------------+
                                   |                           |
                                   |  beforeUnmount            |
                                   |  unmounted                |
                                   |                           |
                                   +---------------------------+

4.11.2 每个钩子的具体用途

初始化阶段

  1. beforeCreate

    • 组件实例刚被创建,数据观测和事件机制尚未初始化
    • 此时无法访问datacomputedmethods
    • 用途:可以做一些初始化前的准备工作,如加载外部资源
  2. created

    • 组件实例创建完成,数据观测和事件机制已初始化
    • 可以访问datacomputedmethods
    • 但DOM尚未生成,$el属性不可用
    • 用途:适合进行数据请求、初始化数据等操作

挂载阶段

  1. beforeMount

    • 模板编译完成,但尚未挂载到DOM
    • $el属性已存在,但尚未替换真实DOM
    • 用途:可以修改模板数据,不会触发重渲染
  2. mounted

    • 组件已挂载到DOM,$el属性指向真实DOM
    • 可以访问和操作DOM元素
    • 用途:适合进行DOM操作、第三方库初始化等

更新阶段

  1. beforeUpdate

    • 数据更新后,DOM更新前触发
    • 可以访问更新前的DOM和数据
    • 用途:可以在更新前获取DOM状态,如滚动位置
  2. updated

    • DOM已更新,可以访问更新后的DOM
    • 用途:适合进行DOM操作、第三方库更新等
    • 注意:避免在updated中修改数据,可能导致无限循环

卸载阶段

  1. beforeUnmount

    • 组件即将卸载,仍可以访问组件实例和DOM
    • 用途:适合清除定时器、取消事件监听、清理第三方库等
  2. unmounted

    • 组件已卸载,所有事件监听已移除,子组件已销毁
    • 用途:可以进行最终的清理工作

错误处理

  1. errorCaptured
    • 捕获子孙组件抛出的错误
    • 可以返回false阻止错误继续向上传播
    • 用途:适合全局错误处理、错误日志收集等
export default {
  // 初始化阶段
  beforeCreate() {
    // 实例初始化后,数据观测之前
    console.log('beforeCreate')
  },
  created() {
    // 实例创建完成,数据观测完成
    // 可以访问this,但DOM未生成
    console.log('created')
  },
  
  // 挂载阶段
  beforeMount() {
    // 模板编译完成,但未挂载到DOM
    console.log('beforeMount')
  },
  mounted() {
    // 实例挂载到DOM
    // 可以访问DOM元素
    console.log('mounted')
  },
  
  // 更新阶段
  beforeUpdate() {
    // 数据更新,DOM重新渲染之前
    console.log('beforeUpdate')
  },
  updated() {
    // 数据更新,DOM重新渲染之后
    console.log('updated')
  },
  
  // 卸载阶段
  beforeUnmount() {
    // 实例卸载之前
    console.log('beforeUnmount')
  },
  unmounted() {
    // 实例卸载完成
    console.log('unmounted')
  },
  
  // 错误处理
  errorCaptured(err, instance, info) {
    // 捕获子孙组件错误
    console.error('errorCaptured:', err, instance, info)
  }
}

在组合式API中使用生命周期钩子

<script setup>
import { onBeforeMount, onMounted, onBeforeUpdate, onUpdated, onBeforeUnmount, onUnmounted, onErrorCaptured } from 'vue'

// 初始化阶段
onMounted(() => {
  console.log('组件已挂载')
})

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

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

onErrorCaptured((err, instance, info) => {
  console.error('捕获到错误:', err, instance, info)
})
</script>
选项式API 组合式API
beforeCreate 无直接对应,使用setup()
created 无直接对应,使用setup()
beforeMount onBeforeMount
mounted onMounted
beforeUpdate onBeforeUpdate
updated onUpdated
beforeUnmount onBeforeUnmount
unmounted onUnmounted
errorCaptured onErrorCaptured

4.11.3 生命周期钩子的实际应用场景

数据请求

export default {
  data() {
    return {
      users: []
    }
  },
  created() {
    // 在created中发起数据请求
    fetch('/api/users')
      .then(response => response.json())
      .then(data => {
        this.users = data
      })
  }
}

第三方库初始化

export default {
  mounted() {
    // 在mounted中初始化第三方库
    this.$nextTick(() => {
      // 确保DOM已完全渲染
      new Swiper(this.$refs.swiper, {
        // 配置项
      })
    })
  },
  beforeUnmount() {
    // 在beforeUnmount中销毁第三方库
    if (this.swiper) {
      this.swiper.destroy()
    }
  }
}

定时器管理

export default {
  data() {
    return {
      count: 0,
      timer: null
    }
  },
  mounted() {
    // 在mounted中启动定时器
    this.timer = setInterval(() => {
      this.count++
    }, 1000)
  },
  beforeUnmount() {
    // 在beforeUnmount中清除定时器
    if (this.timer) {
      clearInterval(this.timer)
    }
  }
}

事件监听管理

export default {
  mounted() {
    // 在mounted中添加事件监听
    window.addEventListener('resize', this.handleResize)
  },
  beforeUnmount() {
    // 在beforeUnmount中移除事件监听
    window.removeEventListener('resize', this.handleResize)
  },
  methods: {
    handleResize() {
      // 处理窗口大小变化
    }
  }
}

4.11.4 异步操作在生命周期中的位置

数据请求

  • 推荐在created中发起数据请求,因为此时组件实例已创建,可以访问datamethods
  • 也可以在mounted中发起数据请求,但会稍晚一些
  • 避免在beforeCreate中发起数据请求,因为此时无法访问组件实例

异步DOM操作

  • 当需要在数据更新后操作DOM时,使用$nextTick
  • $nextTick会在DOM更新后执行回调函数
export default {
  data() {
    return {
      message: 'Hello'
    }
  },
  methods: {
    updateMessage() {
      this.message = 'World'
      // DOM尚未更新
      console.log(this.$refs.msg.textContent) // Hello
      
      this.$nextTick(() => {
        // DOM已更新
        console.log(this.$refs.msg.textContent) // World
      })
    }
  }
}

异步组件加载

  • 使用defineAsyncComponent创建异步组件
  • 可以在组件加载过程中显示加载状态
import { defineAsyncComponent } from 'vue'

const AsyncComponent = defineAsyncComponent({
  // 加载组件
  loader: () => import('./AsyncComponent.vue'),
  // 加载中显示的组件
  loadingComponent: LoadingComponent,
  // 加载超时时间
  timeout: 3000,
  // 加载错误显示的组件
  errorComponent: ErrorComponent
})

总结

Vue组件的生命周期钩子提供了在不同阶段执行代码的机会,通过合理使用这些钩子,可以实现数据请求、DOM操作、第三方库初始化等功能。

在使用生命周期钩子时,需要注意:

  1. 避免在updated中修改数据,可能导致无限循环
  2. 及时清理定时器、事件监听等资源,避免内存泄漏
  3. 异步操作使用$nextTick确保DOM更新完成
  4. 组合式API提供了更灵活的生命周期钩子使用方式

在下一章中,我们将学习Vue.js的过渡与动画效果。

« 上一篇 响应式原理深入 下一篇 » CSS过渡与动画