Vue框架之钩子函数详解
Vue框架之生命周期主要钩子函数详解
- 一、Vue生命周期的整体流程
- 二、创建阶段:初始化组件实例
- 2.1 `beforeCreate`:实例创建前
- 2.2 `created`:实例创建后
- 三、挂载阶段:组件与DOM结合
- 3.1 `beforeMount`:挂载前
- 3.2 `mounted`:挂载后
- 四、更新阶段:数据变化触发重渲染
- 4.1 `beforeUpdate`:更新前
- 4.2 `updated`:更新后
- 五、销毁阶段:组件实例的清理
- 5.1 `beforeDestroy`:销毁前
- 5.2 `destroyed`:销毁后
- 六、特殊场景的钩子函数
- 6.1 keep-alive 相关钩子
- 6.2 错误捕获钩子:`errorCaptured`
- 七、生命周期钩子的执行顺序与实战示例
- 7.1 单组件执行顺序
- 7.2 父子组件执行顺序
- 八、常见问题与避坑指南
- 8.1 避免在`updated`中修改数据
- 8.2 子组件`mounted`晚于父组件`mounted`
- 8.3 清理资源是重中之重
- 九、Vue 3中的生命周期变化
Vue的生命周期是指组件从创建到销毁的整个过程,而生命周期钩子函数则是在这个过程中特定时间点自动执行的函数。掌握这些钩子函数,能让我们在合适的时机执行特定操作(如数据请求、DOM操作、资源清理等),是Vue开发的核心基础。
一、Vue生命周期的整体流程
Vue组件的生命周期可分为4个阶段,每个阶段包含若干钩子函数,整体流程如下:
- 创建阶段:组件实例从初始化到挂载前的过程
- 挂载阶段:组件实例挂载到DOM的过程
- 更新阶段:组件数据变化导致重新渲染的过程
- 销毁阶段:组件实例从DOM中移除并清理资源的过程
二、创建阶段:初始化组件实例
创建阶段是组件实例从无到有的过程,主要完成数据观测(响应式处理)、事件初始化等工作,此时尚未涉及DOM操作。
2.1 beforeCreate
:实例创建前
- 执行时机:Vue实例初始化后(
new Vue()
之后),数据观测(data
、props
)和事件机制初始化前调用。 - 特点:
- 无法访问
data
、props
、methods
中的数据和方法(此时尚未初始化)。 - 不能进行DOM操作(DOM尚未生成)。
- 无法访问
- 适用场景:极少使用,可用于初始化非响应式数据(如临时变量)。
new Vue({data() {return { message: 'Hello' };},beforeCreate() {console.log('beforeCreate:', this.message); // undefined(无法访问data)console.log('methods:', this.getMsg); // undefined(无法访问methods)},methods: {getMsg() { return this.message; }}
});
2.2 created
:实例创建后
- 执行时机:Vue实例初始化完成后调用,此时已完成
data
、props
的响应式处理和methods
的绑定,但尚未开始DOM编译(模板未挂载到DOM)。 - 特点:
- 可访问
data
、props
、methods
中的数据和方法。 - 仍无法进行DOM操作(
$el
属性不存在,DOM未生成)。
- 可访问
- 适用场景:
- 发起初始化数据请求(如获取列表数据)。
- 初始化数据(如对
data
中的数据进行预处理)。 - 绑定自定义事件。
new Vue({el: '#app',data() {return { list: [] };},created() {console.log('created:', this.list); // [](可访问data)console.log('$el:', this.$el); // undefined(DOM未挂载)// 示例:发起数据请求axios.get('/api/list').then(response => {this.list = response.data; // 数据响应后更新list(响应式)});}
});
三、挂载阶段:组件与DOM结合
挂载阶段是组件实例与DOM关联的过程,核心是将编译后的模板挂载到页面中,此时开始具备DOM操作能力。
3.1 beforeMount
:挂载前
- 执行时机:模板编译(解析指令、插值表达式等)完成后,DOM挂载到页面之前调用。
- 特点:
- 已完成模板编译,生成虚拟DOM(但未渲染到页面)。
$el
属性存在(指向即将挂载的DOM元素),但内容仍为原始模板(未替换数据)。- 仍无法操作DOM(数据未渲染,操作无意义)。
- 适用场景:极少使用,可用于获取模板编译前的DOM结构。
new Vue({el: '#app',template: '<div>{{ message }}</div>',data() { return { message: '挂载前' }; },beforeMount() {console.log('beforeMount $el:', this.$el); // <div>{{ message }}</div>(未替换)console.log('页面内容:', document.getElementById('app').innerHTML); // 原始模板}
});
3.2 mounted
:挂载后
- 执行时机:模板挂载到DOM后调用,此时页面已显示渲染后的内容。
- 特点:
- DOM已完全渲染,可通过
$el
或原生DOM API(如document.getElementById
)操作DOM。 - 子组件的
mounted
可能在父组件的mounted
之后执行(因渲染顺序)。
- DOM已完全渲染,可通过
- 适用场景:
- 执行DOM操作(如初始化第三方UI插件:图表、地图等,需依赖DOM元素)。
- 监听DOM事件(如滚动、resize)。
- 若数据请求依赖DOM尺寸(如根据容器宽度请求不同数据),可在此发起请求。
new Vue({el: '#app',data() { return { width: 0 }; },mounted() {// 获取DOM元素宽度this.width = this.$el.offsetWidth;console.log('挂载后宽度:', this.width);// 初始化第三方图表插件(假设页面有<div id="chart"></div>)this.chart = new Chart(document.getElementById('chart'), {type: 'line',data: { labels: ['1月', '2月'], datasets: [{ data: [10, 20] }] }});// 监听滚动事件window.addEventListener('scroll', this.handleScroll);},methods: {handleScroll() { /* 处理滚动逻辑 */ }}
});
四、更新阶段:数据变化触发重渲染
当组件的data
或props
发生变化时,会进入更新阶段,触发重新渲染,此阶段的钩子函数用于监控或干预更新过程。
4.1 beforeUpdate
:更新前
- 执行时机:数据发生变化后,虚拟DOM重新渲染前调用。
- 特点:
- 此时
data
中的数据已更新,但DOM尚未重新渲染(页面显示旧数据)。 - 可获取更新前的DOM状态。
- 此时
- 适用场景:获取更新前的DOM信息(如滚动位置、输入框光标位置),用于更新后恢复状态。
new Vue({el: '#app',data() { return { count: 0 }; },template: '<div>{{ count }} <button @click="count++">+1</button></div>',beforeUpdate() {console.log('更新前data:', this.count); // 新值(如1)console.log('更新前DOM:', this.$el.textContent); // 旧值(如0)}
});
4.2 updated
:更新后
- 执行时机:虚拟DOM重新渲染并更新到页面后调用,此时页面显示最新数据。
- 特点:
data
和DOM均已更新,可获取最新的DOM状态。- 若在
updated
中修改data
,会再次触发更新(可能导致无限循环,需避免)。
- 适用场景:
- 基于最新DOM状态执行操作(如根据新内容调整元素样式)。
- 同步第三方插件数据(如图表数据更新后,重新绘制图表)。
new Vue({el: '#app',data() { return { data: [10, 20] }; },template: '<div>{{ data }}</div>',updated() {console.log('更新后DOM:', this.$el.textContent); // 显示最新data// 若图表数据依赖this.data,更新后重新绘制if (this.chart) {this.chart.data.datasets[0].data = this.data;this.chart.update();}}
});
五、销毁阶段:组件实例的清理
当组件被销毁(如v-if="false"
移除组件、路由切换)时,进入销毁阶段,此阶段的钩子函数用于清理资源,避免内存泄漏。
5.1 beforeDestroy
:销毁前
- 执行时机:组件实例销毁前调用,此时组件仍处于正常工作状态。
- 特点:
- 可访问
data
、methods
、DOM等所有资源。 - 子组件的
beforeDestroy
会在父组件的beforeDestroy
前执行。
- 可访问
- 适用场景:
- 清理定时器、事件监听器(避免组件销毁后仍执行)。
- 取消未完成的请求(避免资源浪费)。
- 解绑自定义事件。
new Vue({el: '#app',data() {return { timer: null };},mounted() {// 启动定时器this.timer = setInterval(() => {console.log('定时器执行中...');}, 1000);// 绑定事件window.addEventListener('resize', this.handleResize);},beforeDestroy() {// 清理定时器clearInterval(this.timer);// 移除事件监听window.removeEventListener('resize', this.handleResize);// 取消未完成的请求(假设使用axios)if (this.source) {this.source.cancel('组件销毁,取消请求');}},methods: {handleResize() { /* 处理窗口大小变化 */ }}
});
5.2 destroyed
:销毁后
- 执行时机:组件实例销毁后调用,此时组件的所有资源已被释放。
- 特点:
- 组件的响应式数据、事件监听、子组件等均已被销毁。
- 仍可访问
$el
,但DOM可能已被移除(取决于销毁方式)。
- 适用场景:极少使用,可用于最终的资源清理或日志记录。
new Vue({destroyed() {console.log('组件已销毁');// 记录销毁日志console.log('组件销毁时间:', new Date().toLocaleString());}
});
六、特殊场景的钩子函数
除上述核心钩子外,Vue还提供了针对特殊场景的钩子函数:
6.1 keep-alive 相关钩子
keep-alive
用于缓存组件(避免频繁创建/销毁),搭配两个专属钩子:
activated
:缓存的组件被激活(显示)时调用。deactivated
:缓存的组件被停用(隐藏)时调用。
<!-- 缓存组件 -->
<keep-alive><component :is="currentComponent"></component>
</keep-alive><script>
new Vue({data() { return { currentComponent: 'ComponentA' }; },components: {ComponentA: {template: '<div>组件A</div>',activated() {console.log('组件A被激活(从缓存中取出)');// 可在此恢复状态(如刷新数据)},deactivated() {console.log('组件A被停用(存入缓存)');// 可在此暂停操作(如暂停视频播放)}}}
});
</script>
6.2 错误捕获钩子:errorCaptured
用于捕获子组件抛出的错误(Vue 2.5+),返回false
可阻止错误向上传播:
new Vue({errorCaptured(err, vm, info) {console.error('捕获子组件错误:', err, '组件:', vm, '信息:', info);// 返回false阻止错误冒泡到控制台return false;}
});
七、生命周期钩子的执行顺序与实战示例
7.1 单组件执行顺序
new Vue({// 创建阶段beforeCreate() { console.log('1. beforeCreate'); },created() { console.log('2. created'); },// 挂载阶段beforeMount() { console.log('3. beforeMount'); },mounted() { console.log('4. mounted'); },// 更新阶段(触发条件:修改data)beforeUpdate() { console.log('5. beforeUpdate'); },updated() { console.log('6. updated'); },// 销毁阶段(触发条件:调用$destroy()或v-if移除)beforeDestroy() { console.log('7. beforeDestroy'); },destroyed() { console.log('8. destroyed'); }
});
执行结果:
初始化时:1→2→3→4
修改数据时:5→6
销毁时:7→8
7.2 父子组件执行顺序
父组件Parent
和子组件Child
的钩子执行顺序:
- 父
beforeCreate
→ 父created
→ 父beforeMount
- 子
beforeCreate
→ 子created
→ 子beforeMount
→ 子mounted
- 父
mounted
- (更新时)父
beforeUpdate
→ 子beforeUpdate
→ 子updated
→ 父updated
- (销毁时)父
beforeDestroy
→ 子beforeDestroy
→ 子destroyed
→ 父destroyed
结论:父组件等待子组件完成后,才会完成自身对应的阶段。
八、常见问题与避坑指南
8.1 避免在updated
中修改数据
// 错误示例:导致无限循环
updated() {this.count++; // 修改data触发更新,再次调用updated,循环往复
}
解决方案:若需根据更新后的数据调整状态,可使用$nextTick
或条件判断限制执行次数。
8.2 子组件mounted
晚于父组件mounted
父组件若需等待子组件挂载完成后执行操作(如获取子组件DOM),需使用$nextTick
或在子组件中通过事件通知父组件:
// 子组件
export default {mounted() {this.$emit('mounted'); // 通知父组件已挂载}
};// 父组件
<child-component @mounted="handleChildMounted"></child-component>
methods: {handleChildMounted() {console.log('子组件已挂载');}
}
8.3 清理资源是重中之重
组件销毁时若未清理定时器、事件监听等,会导致内存泄漏(页面关闭前资源一直占用):
// 错误:未清理定时器
mounted() {setInterval(() => { /* 操作 */ }, 1000); // 组件销毁后仍会执行
}// 正确:在beforeDestroy中清理
beforeDestroy() {clearInterval(this.timer);
}
九、Vue 3中的生命周期变化
Vue 3的Composition API中,生命周期钩子的使用方式有所调整,但核心逻辑一致:
setup()
:替代beforeCreate
和created
(在两者之间执行)。onBeforeMount
:对应beforeMount
。onMounted
:对应mounted
。onBeforeUpdate
:对应beforeUpdate
。onUpdated
:对应updated
。onBeforeUnmount
:对应beforeDestroy
。onUnmounted
:对应destroyed
。
// Vue 3 Composition API示例
import { onMounted, onBeforeUnmount } from 'vue';export default {setup() {let timer;onMounted(() => {timer = setInterval(() => { /* 操作 */ }, 1000);});onBeforeUnmount(() => {clearInterval(timer); // 清理资源});}
};
总结
- 精准控制流程:在合适的阶段执行合适的操作(如
created
请求数据、mounted
操作DOM)。- 避免常见问题:如更新阶段的死循环、销毁阶段的内存泄漏。
- 优化组件性能:合理利用
keep-alive
和缓存钩子,减少不必要的创建/销毁。
若这篇内容帮到你,动动手指支持下!关注不迷路,干货持续输出!
ヾ(´∀ ˋ)ノヾ(´∀ ˋ)ノヾ(´∀ ˋ)ノヾ(´∀ ˋ)ノヾ(´∀ ˋ)ノ