← 返回主页

第10课: 生命周期钩子

什么是生命周期?

生命周期钩子是Vue组件从创建到销毁过程中的特定时刻,允许我们在这些时刻执行代码。

生命周期图示

创建阶段:
  setup() / beforeCreate
  ↓
  created
  ↓
挂载阶段:
  beforeMount
  ↓
  mounted (DOM已创建)
  ↓
更新阶段:
  beforeUpdate
  ↓
  updated
  ↓
卸载阶段:
  beforeUnmount
  ↓
  unmounted

Composition API生命周期

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

// setup函数本身相当于beforeCreate和created

onBeforeMount(() => {
  console.log('组件挂载前')
})

onMounted(() => {
  console.log('组件已挂载,DOM已创建')
  // 适合:访问DOM、发起API请求、设置定时器
})

onBeforeUpdate(() => {
  console.log('数据更新前,DOM还未更新')
})

onUpdated(() => {
  console.log('数据和DOM都已更新')
  // 注意:避免在这里修改状态,可能导致无限循环
})

onBeforeUnmount(() => {
  console.log('组件卸载前')
  // 适合:清理定时器、取消订阅
})

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

Options API生命周期

<script>
export default {
  beforeCreate() {
    console.log('实例初始化后,数据观测前')
  },
  created() {
    console.log('实例创建完成,数据观测完成')
    // 适合:初始化数据、发起API请求
  },
  beforeMount() {
    console.log('挂载开始前')
  },
  mounted() {
    console.log('挂载完成,DOM已创建')
    // 适合:访问DOM、第三方库初始化
  },
  beforeUpdate() {
    console.log('数据更新前')
  },
  updated() {
    console.log('数据更新后')
  },
  beforeUnmount() {
    console.log('卸载前')
  },
  unmounted() {
    console.log('卸载完成')
  }
}
</script>

生命周期对应关系

Options API → Composition API:
- beforeCreate → setup()
- created → setup()
- beforeMount → onBeforeMount()
- mounted → onMounted()
- beforeUpdate → onBeforeUpdate()
- updated → onUpdated()
- beforeUnmount → onBeforeUnmount()
- unmounted → onUnmounted()

onMounted使用场景

<template>
  <div ref="chartRef"></div>
</template>

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

const chartRef = ref(null)

onMounted(() => {
  // 1. 访问DOM元素
  console.log(chartRef.value)

  // 2. 初始化第三方库
  // initChart(chartRef.value)

  // 3. 发起API请求
  fetchData()

  // 4. 添加事件监听
  window.addEventListener('resize', handleResize)
})

function fetchData() {
  console.log('获取数据')
}

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

onUnmounted清理资源

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

let timer = null

onMounted(() => {
  // 设置定时器
  timer = setInterval(() => {
    console.log('定时执行')
  }, 1000)

  // 添加事件监听
  window.addEventListener('scroll', handleScroll)
})

onUnmounted(() => {
  // 清理定时器
  if (timer) {
    clearInterval(timer)
  }

  // 移除事件监听
  window.removeEventListener('scroll', handleScroll)
})

function handleScroll() {
  console.log('滚动')
}
</script>

onBeforeUpdate和onUpdated

<template>
  <div>
    <p ref="textRef">{{ count }}</p>
    <button @click="count++">增加</button>
  </div>
</template>

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

const count = ref(0)
const textRef = ref(null)

onBeforeUpdate(() => {
  console.log('更新前的DOM:', textRef.value?.textContent)
})

onUpdated(() => {
  console.log('更新后的DOM:', textRef.value?.textContent)
})
</script>
注意:避免在onUpdated中修改响应式状态,可能导致无限更新循环。

keep-alive特殊生命周期

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

// 组件被激活时调用
onActivated(() => {
  console.log('组件激活')
  // 适合:恢复数据、重新订阅
})

// 组件被停用时调用
onDeactivated(() => {
  console.log('组件停用')
  // 适合:暂停操作、取消订阅
})
</script>

错误处理生命周期

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

// 捕获子组件错误
onErrorCaptured((err, instance, info) => {
  console.error('捕获到错误:', err)
  console.log('错误组件:', instance)
  console.log('错误信息:', info)

  // 返回false阻止错误继续传播
  return false
})
</script>

实战示例:计时器组件

<template>
  <div>
    <h2>计时器: {{ time }}秒</h2>
    <button @click="start">开始</button>
    <button @click="pause">暂停</button>
    <button @click="reset">重置</button>
  </div>
</template>

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

const time = ref(0)
let timer = null
let isRunning = false

function start() {
  if (!isRunning) {
    isRunning = true
    timer = setInterval(() => {
      time.value++
    }, 1000)
  }
}

function pause() {
  isRunning = false
  if (timer) {
    clearInterval(timer)
    timer = null
  }
}

function reset() {
  pause()
  time.value = 0
}

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

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

实战示例:数据获取

<template>
  <div>
    <div v-if="loading">加载中...</div>
    <div v-else-if="error">错误: {{ error }}</div>
    <div v-else>
      <ul>
        <li v-for="user in users" :key="user.id">
          {{ user.name }}
        </li>
      </ul>
    </div>
  </div>
</template>

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

const users = ref([])
const loading = ref(false)
const error = ref(null)

async function fetchUsers() {
  loading.value = true
  error.value = null

  try {
    const response = await fetch('/api/users')
    users.value = await response.json()
  } catch (e) {
    error.value = e.message
  } finally {
    loading.value = false
  }
}

onMounted(() => {
  fetchUsers()
})
</script>

实战示例:窗口大小监听

<template>
  <div>
    <p>窗口宽度: {{ width }}px</p>
    <p>窗口高度: {{ height }}px</p>
    <p>设备类型: {{ deviceType }}</p>
  </div>
</template>

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

const width = ref(window.innerWidth)
const height = ref(window.innerHeight)

const deviceType = computed(() => {
  if (width.value < 768) return '移动设备'
  if (width.value < 1024) return '平板'
  return '桌面'
})

function handleResize() {
  width.value = window.innerWidth
  height.value = window.innerHeight
}

onMounted(() => {
  window.addEventListener('resize', handleResize)
})

onUnmounted(() => {
  window.removeEventListener('resize', handleResize)
})
</script>

生命周期最佳实践

常见错误

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

const count = ref(0)

// 错误:在onUpdated中修改状态
onUpdated(() => {
  count.value++ // 导致无限循环!
})

// 正确:使用watch
import { watch } from 'vue'
watch(count, (newValue) => {
  console.log('count变化:', newValue)
})

// 错误:忘记清理
onMounted(() => {
  setInterval(() => {
    console.log('定时器')
  }, 1000)
  // 组件卸载后定时器仍在运行!
})

// 正确:在onUnmounted中清理
let timer = null
onMounted(() => {
  timer = setInterval(() => {
    console.log('定时器')
  }, 1000)
})
onUnmounted(() => {
  if (timer) clearInterval(timer)
})
</script>

练习

  1. 创建一个倒计时组件,使用生命周期管理定时器
  2. 创建一个图片懒加载组件,在onMounted中监听滚动事件
  3. 创建一个聊天组件,在onMounted中建立WebSocket连接,在onUnmounted中断开
  4. 使用onActivated和onDeactivated实现页面缓存时的数据刷新