正文
1. Vue生命周期概述
Vue组件的生命周期是指组件从创建到销毁的整个过程,Vue框架在这个过程中提供了一系列的钩子函数,让开发者可以在特定的时间点添加自己的代码逻辑。理解生命周期对于正确使用Vue框架至关重要。
1.1 什么是生命周期?
生命周期是指Vue实例从创建、初始化数据、编译模板、挂载DOM、渲染、更新到销毁的一系列过程。在这个过程中,Vue实例会自动调用生命周期钩子函数,让开发者可以在不同阶段添加自己的代码。
1.2 为什么需要生命周期钩子?
生命周期钩子的主要作用包括:
- 在合适的时机执行初始化操作
- 在数据变化时执行特定逻辑
- 在组件销毁前进行清理工作
- 控制组件更新的时机和方式
- 优化性能,避免不必要的渲染
2. Vue 2.x生命周期钩子
2.1 创建阶段
创建阶段的钩子函数在组件实例被创建时调用。
2.1.1 beforeCreate
export default {
beforeCreate() {
console.log('beforeCreate hook called')
// 此时组件实例刚被创建,data和methods等选项还未初始化
console.log('data:', this.$data) // undefined
console.log('methods:', this.methodName) // undefined
}
}
在这个阶段:
- 实例已经初始化,但数据观测和事件配置都未完成
- 无法访问data、computed、methods、watch中的数据和方法
2.1.2 created
export default {
data() {
return {
message: 'Hello Vue!'
}
},
methods: {
greet() {
return 'Greeting method'
}
},
created() {
console.log('created hook called')
// 此时可以访问data和methods
console.log('data:', this.message) // Hello Vue!
console.log('method:', this.greet()) // Greeting method
// 可以在这里进行API调用
this.fetchInitialData()
},
methods: {
fetchInitialData() {
// 发起API请求获取数据
}
}
}
在这个阶段:
- 实例已完成数据观测、属性和方法的运算、watch/event事件回调的配置
- 可以访问data、computed、methods、watch中的数据和方法
- $el属性还不可用,DOM还未挂载
2.2 挂载阶段
挂载阶段的钩子函数在组件被挂载到DOM时调用。
2.2.1 beforeMount
export default {
beforeMount() {
console.log('beforeMount hook called')
// 此时模板已编译,但还未挂载到DOM
console.log('$el:', this.$el) // 尚未挂载到DOM的元素
}
}
在这个阶段:
- 模板编译完成,虚拟DOM已创建
- 即将挂载到实际DOM,但$el还不是最终的DOM元素
2.2.2 mounted
export default {
mounted() {
console.log('mounted hook called')
// 此时组件已挂载到DOM
console.log('$el:', this.$el) // 已挂载到DOM的元素
// 可以在这里操作DOM
this.$el.querySelector('.some-class').style.color = 'red'
// 可以在这里初始化需要DOM的库
this.initializeChart()
},
methods: {
initializeChart() {
// 初始化图表库,如ECharts、D3等
const chart = echarts.init(this.$el.querySelector('#chart'))
chart.setOption({/*...*/})
}
}
}
在这个阶段:
- 组件已完全挂载到DOM
- 可以访问和操作DOM元素
- 适合初始化依赖DOM的库,如图表、地图等
- 子组件的mounted也已经被调用
2.3 更新阶段
更新阶段的钩子函数在组件的数据发生变化,导致虚拟DOM重新渲染时调用。
2.3.1 beforeUpdate
export default {
data() {
return {
counter: 0
}
},
beforeUpdate() {
console.log('beforeUpdate hook called')
// 此时数据已更新,但DOM还未更新
console.log('data:', this.counter) // 新值
console.log('DOM:', this.$el.textContent) // 旧值
},
methods: {
increment() {
this.counter++
}
}
}
在这个阶段:
- 数据已经更新
- 虚拟DOM正在重新渲染和打补丁
- DOM还未更新
- 适合在更新前访问现有的DOM,如手动移除已添加的事件监听器
2.3.2 updated
export default {
data() {
return {
counter: 0
}
},
updated() {
console.log('updated hook called')
// 此时DOM已更新
console.log('data:', this.counter) // 新值
console.log('DOM:', this.$el.textContent) // 新值
// 可以在这里操作更新后的DOM
if (this.counter > 10) {
this.$el.querySelector('.counter').style.color = 'red'
}
}
}
在这个阶段:
- 组件DOM已经更新
- 可以执行依赖于DOM的操作
- 应避免在此钩子中修改组件状态,可能导致无限更新循环
2.4 销毁阶段
销毁阶段的钩子函数在组件被销毁前后调用。
2.4.1 beforeDestroy
export default {
data() {
return {
timer: null
}
},
created() {
// 设置定时器
this.timer = setInterval(() => {
console.log('Interval running')
}, 1000)
},
beforeDestroy() {
console.log('beforeDestroy hook called')
// 清理定时器
clearInterval(this.timer)
// 移除事件监听器
window.removeEventListener('resize', this.handleResize)
// 取消API请求
if (this.pendingRequest) {
this.pendingRequest.cancel()
}
}
}
在这个阶段:
- 实例仍然完全可用
- 可以访问data、methods等
- 适合清理定时器、事件监听器、取消网络请求等
2.4.2 destroyed
export default {
destroyed() {
console.log('destroyed hook called')
// 组件已完全销毁
}
}
在这个阶段:
- 组件实例已被销毁
- 所有的指令已被解绑
- 所有的事件监听器已被移除
- 所有的子组件实例已被销毁
2.5 特殊钩子
2.5.1 activated和deactivated
这两个钩子用于处理被<keep-alive>
包裹的组件。
export default {
data() {
return {
loadedData: null
}
},
activated() {
console.log('Component activated')
// 组件被激活时,可以刷新数据
this.refreshData()
},
deactivated() {
console.log('Component deactivated')
// 组件被停用时,可以暂停一些操作
this.pauseAnimation()
},
methods: {
refreshData() {
// 刷新数据
},
pauseAnimation() {
// 暂停动画
}
}
}
在这些阶段:
- activated:组件被激活时调用(被缓存的组件再次显示)
- deactivated:组件被停用时调用(被缓存的组件隐藏)
2.5.2 errorCaptured
用于捕获来自子孙组件的错误。
export default {
errorCaptured(err, vm, info) {
console.log('Error captured:', err)
console.log('Component:', vm)
console.log('Info:', info)
// 记录错误
this.logError(err, info)
// 返回false阻止错误继续向上传播
return false
},
methods: {
logError(err, info) {
// 发送错误到日志服务
}
}
}
在这个钩子中:
- 可以捕获来自子孙组件的错误
- 可以记录错误信息
- 可以显示错误状态
- 返回false可以阻止错误继续向上传播
3. Vue 3.x生命周期钩子
Vue 3在保留Vue 2生命周期钩子的同时,为Composition API提供了等效的生命周期钩子函数。
3.1 选项式API生命周期钩子
Vue 3的选项式API生命周期钩子与Vue 2基本相同,但有两点变化:
beforeDestroy
改名为beforeUnmount
destroyed
改名为unmounted
export default {
beforeCreate() {
console.log('beforeCreate')
},
created() {
console.log('created')
},
beforeMount() {
console.log('beforeMount')
},
mounted() {
console.log('mounted')
},
beforeUpdate() {
console.log('beforeUpdate')
},
updated() {
console.log('updated')
},
beforeUnmount() { // 原beforeDestroy
console.log('beforeUnmount')
},
unmounted() { // 原destroyed
console.log('unmounted')
},
activated() {
console.log('activated')
},
deactivated() {
console.log('deactivated')
},
errorCaptured(err, instance, info) {
console.log('errorCaptured')
return false
}
}
3.2 Composition API生命周期钩子
Vue 3的Composition API提供了一组与选项式API等效的生命周期钩子函数。
import {
onBeforeMount,
onMounted,
onBeforeUpdate,
onUpdated,
onBeforeUnmount,
onUnmounted,
onActivated,
onDeactivated,
onErrorCaptured
} from 'vue'
export default {
setup() {
console.log('setup - replaces beforeCreate and created')
onBeforeMount(() => {
console.log('onBeforeMount')
})
onMounted(() => {
console.log('onMounted')
// DOM操作
document.querySelector('.element').style.color = 'red'
})
onBeforeUpdate(() => {
console.log('onBeforeUpdate')
})
onUpdated(() => {
console.log('onUpdated')
})
onBeforeUnmount(() => {
console.log('onBeforeUnmount')
// 清理工作
clearInterval(timer)
})
onUnmounted(() => {
console.log('onUnmounted')
})
onActivated(() => {
console.log('onActivated')
})
onDeactivated(() => {
console.log('onDeactivated')
})
onErrorCaptured((err, instance, info) => {
console.log('onErrorCaptured', err)
return false
})
// 返回要暴露给模板的内容
return {}
}
}
3.2.1 setup函数
setup
函数是Composition API的入口点,它在组件创建过程中很早就被调用,甚至早于beforeCreate
。
import { ref, onMounted } from 'vue'
export default {
props: {
initialCount: Number
},
setup(props, context) {
// props是响应式的,不能解构
console.log(props.initialCount)
// context包含emit, attrs, slots等
const { emit, attrs, slots } = context
// 创建响应式状态
const count = ref(props.initialCount || 0)
// 方法
function increment() {
count.value++
emit('increment', count.value)
}
// 生命周期钩子
onMounted(() => {
console.log('Component mounted')
})
// 返回要暴露给模板的内容
return {
count,
increment
}
}
}
在setup
函数中:
- 无法访问
this
- 可以访问props,但它是响应式的,不应该被解构
- 返回的对象中的属性将暴露给模板和组件实例
3.2.2 Vue 3特有的生命周期钩子
Vue 3还引入了一些新的生命周期钩子:
import {
onRenderTracked,
onRenderTriggered,
onServerPrefetch
} from 'vue'
export default {
setup() {
// 当组件渲染过程中追踪到响应式依赖时调用
onRenderTracked((event) => {
console.log('Dependency tracked:', event)
})
// 当响应式依赖触发组件重新渲染时调用
onRenderTriggered((event) => {
console.log('Render triggered by:', event)
})
// 服务器端渲染相关,在服务器端渲染期间获取数据
onServerPrefetch(async () => {
// 等待异步数据
await fetchData()
})
}
}
这些钩子主要用于调试和服务器端渲染优化。
4. 生命周期钩子的实际应用
4.1 数据获取
4.1.1 在created/setup中获取数据
// Vue 2 - 选项式API
export default {
data() {
return {
posts: [],
loading: true,
error: null
}
},
created() {
this.fetchData()
},
methods: {
async fetchData() {
try {
this.loading = true
const response = await fetch('https://api.example.com/posts')
this.posts = await response.json()
} catch (err) {
this.error = err.message
} finally {
this.loading = false
}
}
}
}
// Vue 3 - Composition API
import { ref, onMounted } from 'vue'
export default {
setup() {
const posts = ref([])
const loading = ref(true)
const error = ref(null)
const fetchData = async () => {
try {
loading.value = true
const response = await fetch('https://api.example.com/posts')
posts.value = await response.json()
} catch (err) {
error.value = err.message
} finally {
loading.value = false
}
}
// 在setup中直接调用或在onMounted中调用
fetchData()
return { posts, loading, error }
}
}
4.1.2 在路由变化时重新获取数据
// Vue 2
export default {
data() {
return {
post: null,
loading: true
}
},
created() {
this.fetchData()
},
watch: {
// 监听路由参数变化
'$route.params.id': function(newId) {
this.fetchData()
}
},
methods: {
async fetchData() {
this.loading = true
const response = await fetch(`https://api.example.com/posts/${this.$route.params.id}`)
this.post = await response.json()
this.loading = false
}
}
}
// Vue 3
import { ref, watch } from 'vue'
import { useRoute } from 'vue-router'
export default {
setup() {
const route = useRoute()
const post = ref(null)
const loading = ref(true)
const fetchData = async () => {
loading.value = true
const response = await fetch(`https://api.example.com/posts/${route.params.id}`)
post.value = await response.json()
loading.value = false
}
// 首次加载
fetchData()
// 监听路由参数变化
watch(() => route.params.id, fetchData)
return { post, loading }
}
}
4.2 DOM操作和第三方库集成
4.2.1 在mounted中初始化第三方库
// Vue 2
export default {
data() {
return {
chart: null,
chartData: [10, 20, 30, 40, 50]
}
},
mounted() {
this.initChart()
},
beforeDestroy() {
// 清理图表实例
if (this.chart) {
this.chart.dispose()
}
},
methods: {
initChart() {
const chartDom = this.$refs.chartContainer
this.chart = echarts.init(chartDom)
this.chart.setOption({
xAxis: { type: 'category', data: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri'] },
yAxis: { type: 'value' },
series: [{ data: this.chartData, type: 'bar' }]
})
}
}
}
// Vue 3
import { ref, onMounted, onBeforeUnmount } from 'vue'
import * as echarts from 'echarts'
export default {
setup() {
const chartContainer = ref(null)
const chart = ref(null)
const chartData = ref([10, 20, 30, 40, 50])
onMounted(() => {
chart.value = echarts.init(chartContainer.value)
chart.value.setOption({
xAxis: { type: 'category', data: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri'] },
yAxis: { type: 'value' },
series: [{ data: chartData.value, type: 'bar' }]
})
})
onBeforeUnmount(() => {
if (chart.value) {
chart.value.dispose()
}
})
return { chartContainer, chartData }
}
}
4.2.2 在updated中更新第三方库
// Vue 2
export default {
data() {
return {
chart: null,
chartData: [10, 20, 30, 40, 50]
}
},
mounted() {
this.initChart()
},
updated() {
// 当数据变化时更新图表
if (this.chart) {
this.chart.setOption({
series: [{ data: this.chartData }]
})
}
},
methods: {
initChart() { /* 同上 */ },
updateData() {
// 更新数据
this.chartData = [30, 40, 50, 60, 70]
}
}
}
// Vue 3
import { ref, onMounted, onUpdated, onBeforeUnmount } from 'vue'
import * as echarts from 'echarts'
export default {
setup() {
const chartContainer = ref(null)
const chart = ref(null)
const chartData = ref([10, 20, 30, 40, 50])
onMounted(() => {
chart.value = echarts.init(chartContainer.value)
chart.value.setOption({
xAxis: { type: 'category', data: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri'] },
yAxis: { type: 'value' },
series: [{ data: chartData.value, type: 'bar' }]
})
})
onUpdated(() => {
if (chart.value) {
chart.value.setOption({
series: [{ data: chartData.value }]
})
}
})
function updateData() {
chartData.value = [30, 40, 50, 60, 70]
}
onBeforeUnmount(() => {
if (chart.value) {
chart.value.dispose()
}
})
return { chartContainer, chartData, updateData }
}
}
4.3 资源清理
4.3.1 清理定时器和事件监听器
// Vue 2
export default {
data() {
return {
timer: null,
count: 0
}
},
mounted() {
// 设置定时器
this.timer = setInterval(() => {
this.count++
}, 1000)
// 添加事件监听器
window.addEventListener('resize', this.handleResize)
},
beforeDestroy() {
// 清理定时器
clearInterval(this.timer)
// 移除事件监听器
window.removeEventListener('resize', this.handleResize)
},
methods: {
handleResize() {
console.log('Window resized')
}
}
}
// Vue 3
import { ref, onMounted, onBeforeUnmount } from 'vue'
export default {
setup() {
const count = ref(0)
let timer = null
function handleResize() {
console.log('Window resized')
}
onMounted(() => {
// 设置定时器
timer = setInterval(() => {
count.value++
}, 1000)
// 添加事件监听器
window.addEventListener('resize', handleResize)
})
onBeforeUnmount(() => {
// 清理定时器
clearInterval(timer)
// 移除事件监听器
window.removeEventListener('resize', handleResize)
})
return { count }
}
}
4.3.2 取消异步请求
// Vue 2
export default {
data() {
return {
controller: null,
data: null
}
},
created() {
this.fetchData()
},
beforeDestroy() {
// 取消请求
if (this.controller) {
this.controller.abort()
}
},
methods: {
async fetchData() {
// 创建AbortController
this.controller = new AbortController()
try {
const response = await fetch('https://api.example.com/data', {
signal: this.controller.signal
})
this.data = await response.json()
} catch (err) {
if (err.name === 'AbortError') {
console.log('Fetch aborted')
} else {
console.error('Fetch error:', err)
}
}
}
}
}
// Vue 3
import { ref, onMounted, onBeforeUnmount } from 'vue'
export default {
setup() {
const data = ref(null)
let controller = null
async function fetchData() {
// 创建AbortController
controller = new AbortController()
try {
const response = await fetch('https://api.example.com/data', {
signal: controller.signal
})
data.value = await response.json()
} catch (err) {
if (err.name === 'AbortError') {
console.log('Fetch aborted')
} else {
console.error('Fetch error:', err)
}
}
}
onMounted(() => {
fetchData()
})
onBeforeUnmount(() => {
// 取消请求
if (controller) {
controller.abort()
}
})
return { data }
}
}
4.4 缓存组件的生命周期管理
4.4.1 使用keep-alive和相关钩子
// Vue 2
export default {
data() {
return {
intervalId: null,
time: new Date()
}
},
activated() {
// 组件被激活时启动定时器
this.intervalId = setInterval(() => {
this.time = new Date()
}, 1000)
// 可以刷新数据
this.refreshData()
},
deactivated() {
// 组件被停用时清除定时器
clearInterval(this.intervalId)
},
methods: {
refreshData() {
// 刷新数据的逻辑
}
}
}
// Vue 3
import { ref, onActivated, onDeactivated } from 'vue'
export default {
setup() {
const time = ref(new Date())
let intervalId = null
onActivated(() => {
// 组件被激活时启动定时器
intervalId = setInterval(() => {
time.value = new Date()
}, 1000)
// 可以刷新数据
refreshData()
})
onDeactivated(() => {
// 组件被停用时清除定时器
clearInterval(intervalId)
})
function refreshData() {
// 刷新数据的逻辑
}
return { time, refreshData }
}
}
在父组件中使用keep-alive:
<template>
<keep-alive>
<component :is="currentComponent"></component>
</keep-alive>
</template>
<script>
export default {
data() {
return {
currentComponent: 'ComponentA'
}
}
}
</script>
5. 生命周期钩子的最佳实践
5.1 选择正确的钩子
根据需求选择合适的生命周期钩子:
需求 | 推荐钩子 |
---|---|
初始化非响应式变量 | beforeCreate |
访问响应式数据 | created |
访问DOM元素 | mounted |
监听组件数据变化 | updated |
清理资源 | beforeUnmount |
处理缓存组件 | activated/deactivated |
5.2 性能优化技巧
5.2.1 避免在updated中修改数据
// 错误示例 - 可能导致无限循环
export default {
data() {
return { counter: 0 }
},
updated() {
// 这会触发新的更新,导致无限循环
this.counter++
}
}
// 正确示例 - 使用条件判断
export default {
data() {
return {
counter: 0,
needsUpdate: true
}
},
updated() {
if (this.needsUpdate) {
this.needsUpdate = false
this.performOneTimeUpdate()
}
},
methods: {
performOneTimeUpdate() {
// 执行一次性更新
}
}
}
5.2.2 使用计算属性代替updated钩子
// 不推荐 - 在updated中计算派生值
export default {
data() {
return {
items: [1, 2, 3],
total: 0
}
},
updated() {
this.total = this.items.reduce((sum, item) => sum + item, 0)
}
}
// 推荐 - 使用计算属性
export default {
data() {
return {
items: [1, 2, 3]
}
},
computed: {
total() {
return this.items.reduce((sum, item) => sum + item, 0)
}
}
}
5.2.3 延迟执行非关键操作
export default {
mounted() {
// 立即执行关键操作
this.initCriticalFeatures()
// 延迟执行非关键操作
this.$nextTick(() => {
this.initNonCriticalFeatures()
})
// 或使用setTimeout进一步延迟
setTimeout(() => {
this.initDeferredFeatures()
}, 100)
}
}
结语
感谢您的阅读!期待您的一键三连!欢迎指正!