生命周期钩子 #

一、生命周期概览 #

1.1 生命周期图示 #

text
创建阶段
├── beforeCreate (选项式API)
├── created (选项式API)
├── setup (组合式API)
│
挂载阶段
├── onBeforeMount
├── onMounted
│
更新阶段
├── onBeforeUpdate
├── onUpdated
│
卸载阶段
├── onBeforeUnmount
├── onUnmounted
│
特殊钩子
├── onErrorCaptured
├── onRenderTracked
├── onRenderTriggered
└── onActivated / onDeactivated (keep-alive)

1.2 API对照表 #

选项式API 组合式API 说明
beforeCreate setup() 实例初始化前
created setup() 实例创建后
beforeMount onBeforeMount 挂载前
mounted onMounted 挂载后
beforeUpdate onBeforeUpdate 更新前
updated onUpdated 更新后
beforeUnmount onBeforeUnmount 卸载前
unmounted onUnmounted 卸载后
errorCaptured onErrorCaptured 捕获错误

二、创建阶段 #

2.1 setup替代beforeCreate和created #

vue
<script setup>
import { ref } from 'vue'

// setup在beforeCreate和created之间执行
// 此时组件实例已创建,但DOM还未挂载

const count = ref(0)

// 相当于created中的代码
console.log('组件已创建')
</script>

2.2 初始化操作 #

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

// 初始化响应式数据
const count = ref(0)
const name = ref('张三')

// 初始化计算属性
const doubled = computed(() => count.value * 2)

// 初始化方法
function increment() {
  count.value++
}

// 调用初始化函数
initializeApp()

async function initializeApp() {
  // 加载初始数据
  const data = await fetchInitialData()
  // 处理数据
}
</script>

三、挂载阶段 #

3.1 onBeforeMount #

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

const element = ref(null)

onBeforeMount(() => {
  // DOM还未挂载
  console.log('挂载前')
  console.log(element.value)  // null
})
</script>

3.2 onMounted #

vue
<template>
  <div ref="container">
    <canvas ref="canvas"></canvas>
  </div>
</template>

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

const container = ref(null)
const canvas = ref(null)

onMounted(() => {
  // DOM已挂载,可以访问DOM元素
  console.log(container.value)
  console.log(canvas.value)
  
  // 初始化第三方库
  initChart()
  
  // 添加事件监听
  window.addEventListener('resize', handleResize)
})

function initChart() {
  const ctx = canvas.value.getContext('2d')
  // 绑制图表
}

function handleResize() {
  console.log('窗口大小变化')
}
</script>

3.3 常见使用场景 #

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

// 1. 获取DOM元素
const inputRef = ref(null)
onMounted(() => {
  inputRef.value.focus()
})

// 2. 发送网络请求
const data = ref(null)
const loading = ref(false)

onMounted(async () => {
  loading.value = true
  try {
    const response = await fetch('/api/data')
    data.value = await response.json()
  } finally {
    loading.value = false
  }
})

// 3. 初始化第三方库
onMounted(() => {
  // 初始化图表库
  const chart = new Chart(ctx, config)
  
  // 初始化地图
  const map = new Map.Map('container', options)
})

// 4. 添加事件监听
onMounted(() => {
  window.addEventListener('scroll', handleScroll)
  document.addEventListener('keydown', handleKeydown)
})
</script>

四、更新阶段 #

4.1 onBeforeUpdate #

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

const list = ref([1, 2, 3])

onBeforeUpdate(() => {
  // DOM更新前
  console.log('更新前,DOM还未重新渲染')
})
</script>

4.2 onUpdated #

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

const count = ref(0)

onUpdated(() => {
  // DOM更新后
  console.log('DOM已更新')
  
  // 注意:不要在这里修改响应式数据,可能导致无限循环
  // count.value++  // 危险!
})
</script>

4.3 使用场景 #

vue
<template>
  <div ref="scrollContainer">
    <div v-for="item in messages" :key="item.id">
      {{ item.text }}
    </div>
  </div>
</template>

<script setup>
import { ref, onUpdated, nextTick } from 'vue'

const messages = ref([])
const scrollContainer = ref(null)

onUpdated(() => {
  // 新消息后自动滚动到底部
  scrollToBottom()
})

function scrollToBottom() {
  nextTick(() => {
    scrollContainer.value.scrollTop = scrollContainer.value.scrollHeight
  })
}

function addMessage(text) {
  messages.value.push({
    id: Date.now(),
    text
  })
}
</script>

五、卸载阶段 #

5.1 onBeforeUnmount #

vue
<script setup>
import { onBeforeUnmount } from 'vue'

onBeforeUnmount(() => {
  // 组件卸载前
  console.log('即将卸载')
  
  // 可以在这里做一些确认操作
  if (hasUnsavedChanges.value) {
    // 提示用户保存
  }
})
</script>

5.2 onUnmounted #

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

const timer = ref(null)

onMounted(() => {
  // 设置定时器
  timer.value = setInterval(() => {
    console.log('定时器执行')
  }, 1000)
  
  // 添加事件监听
  window.addEventListener('resize', handleResize)
  document.addEventListener('click', handleClick)
})

onUnmounted(() => {
  // 清理定时器
  clearInterval(timer.value)
  
  // 移除事件监听
  window.removeEventListener('resize', handleResize)
  document.removeEventListener('click', handleClick)
  
  // 取消网络请求
  controller.abort()
  
  // 销毁第三方实例
  chart.destroy()
  map.destroy()
})

function handleResize() {}
function handleClick() {}
</script>

5.3 清理资源示例 #

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

// WebSocket连接
let ws = null

onMounted(() => {
  ws = new WebSocket('wss://example.com/socket')
  ws.onmessage = (event) => {
    console.log('收到消息:', event.data)
  }
})

onUnmounted(() => {
  if (ws) {
    ws.close()
  }
})

// 媒体流
const videoRef = ref(null)
let stream = null

onMounted(async () => {
  stream = await navigator.mediaDevices.getUserMedia({ video: true })
  videoRef.value.srcObject = stream
})

onUnmounted(() => {
  if (stream) {
    stream.getTracks().forEach(track => track.stop())
  }
})
</script>

六、keep-alive相关钩子 #

6.1 onActivated #

vue
<script setup>
import { onActivated } from 'vue'

onActivated(() => {
  // 组件被激活时(从缓存中恢复)
  console.log('组件激活')
  
  // 刷新数据
  refreshData()
  
  // 恢复滚动位置
  restoreScrollPosition()
})
</script>

6.2 onDeactivated #

vue
<script setup>
import { onDeactivated } from 'vue'

onDeactivated(() => {
  // 组件被停用时(进入缓存)
  console.log('组件停用')
  
  // 保存滚动位置
  saveScrollPosition()
  
  // 暂停视频
  pauseVideo()
})
</script>

6.3 完整示例 #

vue
<template>
  <div class="page" ref="pageRef">
    <h2>{{ title }}</h2>
    <div v-for="item in list" :key="item.id">
      {{ item.name }}
    </div>
  </div>
</template>

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

const pageRef = ref(null)
const title = ref('列表页')
const list = ref([])
const scrollPosition = ref(0)

onMounted(async () => {
  await loadList()
})

onActivated(() => {
  // 恢复滚动位置
  pageRef.value.scrollTop = scrollPosition.value
  
  // 刷新数据(可选)
  // loadList()
})

onDeactivated(() => {
  // 保存滚动位置
  scrollPosition.value = pageRef.value.scrollTop
})

async function loadList() {
  const response = await fetch('/api/list')
  list.value = await response.json()
}
</script>

七、错误处理钩子 #

7.1 onErrorCaptured #

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

const error = ref(null)

onErrorCaptured((err, instance, info) => {
  // 捕获后代组件的错误
  console.error('捕获到错误:', err)
  console.error('组件实例:', instance)
  console.error('错误信息:', info)
  
  error.value = err.message
  
  // 返回false阻止错误继续传播
  return false
})
</script>

7.2 全局错误处理 #

javascript
// main.js
const app = createApp(App)

app.config.errorHandler = (err, instance, info) => {
  console.error('全局错误:', err)
  // 上报错误
  reportError(err)
}

app.mount('#app')

八、调试钩子 #

8.1 onRenderTracked #

vue
<script setup>
import { onRenderTracked } from 'vue'

onRenderTracked((e) => {
  // 组件渲染时追踪的响应式依赖
  console.log('追踪依赖:', e)
})
</script>

8.2 onRenderTriggered #

vue
<script setup>
import { onRenderTriggered } from 'vue'

onRenderTriggered((e) => {
  // 触发组件重新渲染的依赖变化
  console.log('触发渲染:', e)
})
</script>

九、生命周期最佳实践 #

9.1 常见模式 #

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

// 模式1:事件监听
onMounted(() => {
  window.addEventListener('resize', handleResize)
})
onUnmounted(() => {
  window.removeEventListener('resize', handleResize)
})

// 模式2:定时器
let timer = null
onMounted(() => {
  timer = setInterval(pollData, 5000)
})
onUnmounted(() => {
  clearInterval(timer)
})

// 模式3:网络请求
const controller = new AbortController()
onMounted(async () => {
  try {
    const response = await fetch('/api/data', {
      signal: controller.signal
    })
    // 处理数据
  } catch (e) {
    if (e.name !== 'AbortError') {
      console.error(e)
    }
  }
})
onUnmounted(() => {
  controller.abort()
})
</script>

9.2 使用组合式函数封装 #

javascript
// useEventListener.js
import { onMounted, onUnmounted } from 'vue'

export function useEventListener(target, event, callback) {
  onMounted(() => {
    target.addEventListener(event, callback)
  })
  
  onUnmounted(() => {
    target.removeEventListener(event, callback)
  })
}

// 使用
import { useEventListener } from './useEventListener'

useEventListener(window, 'resize', handleResize)
useEventListener(document, 'click', handleClick)
javascript
// useInterval.js
import { ref, onUnmounted } from 'vue'

export function useInterval(callback, delay) {
  const timer = ref(null)
  
  function start() {
    stop()
    timer.value = setInterval(callback, delay)
  }
  
  function stop() {
    if (timer.value) {
      clearInterval(timer.value)
      timer.value = null
    }
  }
  
  onUnmounted(stop)
  
  return { start, stop }
}

十、总结 #

钩子使用场景 #

钩子 使用场景
setup 初始化数据、计算属性
onMounted DOM操作、网络请求、事件监听
onUpdated DOM更新后操作
onUnmounted 清理定时器、事件监听、网络请求
onActivated keep-alive激活时恢复状态
onDeactivated keep-alive停用时保存状态
onErrorCaptured 捕获错误

最佳实践:

  • 在onMounted中进行DOM操作
  • 在onUnmounted中清理资源
  • 避免在onUpdated中修改响应式数据
  • 使用组合式函数封装重复逻辑
  • keep-alive组件使用onActivated/onDeactivated
最后更新:2026-03-26