生命周期
Vue组件的生命周期是指组件从创建到销毁的整个过程,Vue框架在这个过程中提供了一系列的钩子函数,允许开发者在不同阶段执行自定义的逻辑。本文将详细介绍Vue 2和Vue 3的生命周期钩子及其使用场景。
Vue 2的生命周期
1. 生命周期图示
Vue 2的生命周期可以分为以下几个阶段:
- 初始化阶段:组件实例被创建,初始化数据和事件。
- 挂载阶段:组件被挂载到DOM上,开始渲染。
- 更新阶段:组件数据发生变化,重新渲染。
- 销毁阶段:组件被销毁,清理资源。
2. 生命周期钩子
2.1 初始化阶段
- beforeCreate:在实例初始化之后,数据观测(data observer)和event/watcher事件配置之前被调用。
- created:在实例创建完成后被调用。此时,实例已完成以下配置:数据观测(data observer),属性和方法的运算,watch/event事件回调。但挂载阶段还未开始,$el属性目前不可见。
2.2 挂载阶段
- beforeMount:在挂载开始之前被调用:相关的render函数首次被调用。
- mounted:el被新创建的vm.$el替换,并挂载到实例上去之后调用该钩子。如果root实例挂载了一个文档内元素,当mounted被调用时vm.$el也在文档内。
2.3 更新阶段
- beforeUpdate:数据更新时调用,发生在虚拟DOM打补丁之前。这里适合在更新之前访问现有的DOM,比如手动移除已添加的事件监听器。
- updated:由于数据更改导致的虚拟DOM重新渲染和打补丁,在这之后会调用该钩子。当这个钩子被调用时,组件DOM已经更新,所以你现在可以执行依赖于DOM的操作。
2.4 销毁阶段
- beforeDestroy:实例销毁之前调用。在这一步,实例仍然完全可用。
- destroyed:Vue实例销毁后调用。调用后,Vue实例指示的所有东西都会解绑定,所有的事件监听器会被移除,所有的子实例也会被销毁。
2.5 其他钩子
- activated:keep-alive组件激活时调用。
- deactivated:keep-alive组件停用时调用。
- errorCaptured:当捕获一个来自子孙组件的错误时被调用。此钩子会收到三个参数:错误对象、发生错误的组件实例以及一个包含错误来源信息的字符串。此钩子可以返回false以阻止该错误继续向上传播。
3. 生命周期钩子的使用
// Vue 2组件生命周期示例
new Vue({
el: '#app',
data: {
message: 'Hello Vue!'
},
beforeCreate() {
console.log('beforeCreate: 实例初始化之前');
console.log('data:', this.message); // undefined
console.log('el:', this.$el); // undefined
},
created() {
console.log('created: 实例创建完成');
console.log('data:', this.message); // Hello Vue!
console.log('el:', this.$el); // undefined
},
beforeMount() {
console.log('beforeMount: 挂载开始之前');
console.log('el:', this.$el); // <div id="app"></div>
},
mounted() {
console.log('mounted: 挂载完成');
console.log('el:', this.$el); // <div id="app">Hello Vue!</div>
},
beforeUpdate() {
console.log('beforeUpdate: 数据更新之前');
console.log('message:', this.message); // 新值
},
updated() {
console.log('updated: 数据更新完成');
console.log('message:', this.message); // 新值
},
beforeDestroy() {
console.log('beforeDestroy: 实例销毁之前');
},
destroyed() {
console.log('destroyed: 实例销毁完成');
}
});4. 生命周期钩子的执行顺序
对于父组件和子组件,生命周期钩子的执行顺序如下:
- 父组件:beforeCreate
- 父组件:created
- 父组件:beforeMount
- 子组件:beforeCreate
- 子组件:created
- 子组件:beforeMount
- 子组件:mounted
- 父组件:mounted
- 父组件:beforeUpdate
- 子组件:beforeUpdate
- 子组件:updated
- 父组件:updated
- 父组件:beforeDestroy
- 子组件:beforeDestroy
- 子组件:destroyed
- 父组件:destroyed
Vue 3的生命周期
1. 生命周期图示
Vue 3的生命周期与Vue 2类似,但使用了组合式API,钩子函数的名称和使用方式有所不同。
2. 生命周期钩子
Vue 3的生命周期钩子可以通过 on 前缀的函数来使用,这些函数需要从 vue 中导入。
2.1 初始化阶段
- setup:组合式API的入口点,在组件创建之前执行,替代了Vue 2的
beforeCreate和created钩子。
2.2 挂载阶段
- onBeforeMount:在挂载开始之前被调用,对应Vue 2的
beforeMount。 - onMounted:在组件挂载完成后被调用,对应Vue 2的
mounted。
2.3 更新阶段
- onBeforeUpdate:在数据更新之前被调用,对应Vue 2的
beforeUpdate。 - onUpdated:在数据更新完成后被调用,对应Vue 2的
updated。
2.4 销毁阶段
- onBeforeUnmount:在组件卸载之前被调用,对应Vue 2的
beforeDestroy。 - onUnmounted:在组件卸载完成后被调用,对应Vue 2的
destroyed。
2.5 其他钩子
- onActivated:keep-alive组件激活时调用,对应Vue 2的
activated。 - onDeactivated:keep-alive组件停用时调用,对应Vue 2的
deactivated。 - onErrorCaptured:当捕获一个来自子孙组件的错误时被调用,对应Vue 2的
errorCaptured。 - onRenderTracked:在响应式依赖被收集时调用,仅在开发模式下使用。
- onRenderTriggered:在响应式依赖被触发时调用,仅在开发模式下使用。
3. 生命周期钩子的使用
// Vue 3组件生命周期示例
import { ref, onBeforeMount, onMounted, onBeforeUpdate, onUpdated, onBeforeUnmount, onUnmounted } from 'vue';
export default {
setup() {
const message = ref('Hello Vue 3!');
console.log('setup: 组件初始化');
onBeforeMount(() => {
console.log('onBeforeMount: 挂载开始之前');
});
onMounted(() => {
console.log('onMounted: 挂载完成');
});
onBeforeUpdate(() => {
console.log('onBeforeUpdate: 数据更新之前');
});
onUpdated(() => {
console.log('onUpdated: 数据更新完成');
});
onBeforeUnmount(() => {
console.log('onBeforeUnmount: 组件卸载之前');
});
onUnmounted(() => {
console.log('onUnmounted: 组件卸载完成');
});
return {
message
};
}
};4. 生命周期钩子的执行顺序
Vue 3的生命周期钩子执行顺序与Vue 2类似,但由于使用了组合式API,setup 函数会在所有生命周期钩子之前执行。
生命周期钩子的使用场景
1. 初始化阶段
- beforeCreate/created (Vue 2) / setup (Vue 3):
- 初始化数据和事件。
- 发起API请求,获取初始数据。
- 配置全局事件监听器。
2. 挂载阶段
beforeMount (Vue 2) / onBeforeMount (Vue 3):
- 访问DOM元素,进行初始化操作。
- 配置第三方库。
mounted (Vue 2) / onMounted (Vue 3):
- 执行DOM操作,如获取元素尺寸、添加事件监听器。
- 启动定时器或WebSocket连接。
- 发起依赖DOM的API请求。
3. 更新阶段
beforeUpdate (Vue 2) / onBeforeUpdate (Vue 3):
- 访问更新前的DOM状态。
- 清理不需要的事件监听器。
updated (Vue 2) / onUpdated (Vue 3):
- 执行依赖DOM更新的操作,如滚动到指定位置。
- 重新计算DOM元素的尺寸。
4. 销毁阶段
beforeDestroy (Vue 2) / onBeforeUnmount (Vue 3):
- 清理定时器和WebSocket连接。
- 移除事件监听器。
- 取消未完成的API请求。
destroyed (Vue 2) / onUnmounted (Vue 3):
- 执行最终的清理操作。
5. 其他场景
activated (Vue 2) / onActivated (Vue 3):
- 当keep-alive组件被激活时,执行恢复状态的操作。
deactivated (Vue 2) / onDeactivated (Vue 3):
- 当keep-alive组件被停用时,执行暂停状态的操作。
errorCaptured (Vue 2) / onErrorCaptured (Vue 3):
- 捕获和处理组件树中的错误。
- 记录错误信息。
生命周期钩子的最佳实践
1. 合理使用生命周期钩子
- 初始化数据:在
created(Vue 2) 或setup(Vue 3) 中初始化数据和发起API请求。 - DOM操作:在
mounted(Vue 2) 或onMounted(Vue 3) 中执行DOM操作。 - 清理资源:在
beforeDestroy(Vue 2) 或onBeforeUnmount(Vue 3) 中清理资源。
2. 避免在生命周期钩子中执行耗时操作
- 初始化阶段:避免在
created或setup中执行耗时操作,可能会阻塞组件的渲染。 - 更新阶段:避免在
updated中执行耗时操作,可能会影响用户体验。
3. 使用异步操作
- API请求:使用
async/await或 Promise 处理异步API请求。 - DOM操作:对于依赖DOM的操作,确保在
mounted或onMounted中执行。
4. 避免在生命周期钩子中修改数据
- 更新阶段:避免在
updated中修改数据,可能会导致无限循环更新。 - 销毁阶段:避免在
destroyed或onUnmounted中修改数据,此时组件已经被销毁。
生命周期钩子的实际应用
1. 数据获取
// Vue 2
new Vue({
data() {
return {
users: []
};
},
created() {
// 发起API请求获取数据
fetch('https://api.example.com/users')
.then(response => response.json())
.then(data => {
this.users = data;
})
.catch(error => {
console.error('Error fetching users:', error);
});
}
});
// Vue 3
import { ref, onMounted } from 'vue';
export default {
setup() {
const users = ref([]);
onMounted(async () => {
try {
const response = await fetch('https://api.example.com/users');
const data = await response.json();
users.value = data;
} catch (error) {
console.error('Error fetching users:', error);
}
});
return {
users
};
}
};2. DOM操作
// Vue 2
new Vue({
mounted() {
// 获取元素尺寸
this.$nextTick(() => {
const width = this.$el.offsetWidth;
const height = this.$el.offsetHeight;
console.log(`Element size: ${width}x${height}`);
});
}
});
// Vue 3
import { ref, onMounted } from 'vue';
export default {
setup() {
const elementRef = ref(null);
onMounted(() => {
// 获取元素尺寸
if (elementRef.value) {
const width = elementRef.value.offsetWidth;
const height = elementRef.value.offsetHeight;
console.log(`Element size: ${width}x${height}`);
}
});
return {
elementRef
};
}
};3. 定时器和事件监听器
// Vue 2
new Vue({
data() {
return {
count: 0,
timer: null
};
},
mounted() {
// 启动定时器
this.timer = setInterval(() => {
this.count++;
}, 1000);
// 添加事件监听器
window.addEventListener('resize', this.handleResize);
},
beforeDestroy() {
// 清理定时器
if (this.timer) {
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;
const handleResize = () => {
console.log('Window resized');
};
onMounted(() => {
// 启动定时器
timer = setInterval(() => {
count.value++;
}, 1000);
// 添加事件监听器
window.addEventListener('resize', handleResize);
});
onBeforeUnmount(() => {
// 清理定时器
if (timer) {
clearInterval(timer);
}
// 移除事件监听器
window.removeEventListener('resize', handleResize);
});
return {
count
};
}
};4. 第三方库集成
// Vue 2
new Vue({
mounted() {
// 初始化第三方库
this.chart = new Chart(this.$refs.canvas, {
type: 'bar',
data: {
labels: ['Red', 'Blue', 'Yellow'],
datasets: [{
label: 'Votes',
data: [12, 19, 3]
}]
}
});
},
beforeDestroy() {
// 销毁第三方库实例
if (this.chart) {
this.chart.destroy();
}
}
});
// Vue 3
import { ref, onMounted, onBeforeUnmount } from 'vue';
export default {
setup() {
const canvasRef = ref(null);
let chart = null;
onMounted(() => {
// 初始化第三方库
chart = new Chart(canvasRef.value, {
type: 'bar',
data: {
labels: ['Red', 'Blue', 'Yellow'],
datasets: [{
label: 'Votes',
data: [12, 19, 3]
}]
}
});
});
onBeforeUnmount(() => {
// 销毁第三方库实例
if (chart) {
chart.destroy();
}
});
return {
canvasRef
};
}
};面试常见问题
1. Vue 2和Vue 3的生命周期有什么区别?
Vue 2和Vue 3的生命周期主要区别如下:
| Vue 2 | Vue 3 | 说明 |
|---|---|---|
| beforeCreate | setup | Vue 3中setup替代了beforeCreate和created |
| created | setup | Vue 3中setup替代了beforeCreate和created |
| beforeMount | onBeforeMount | 名称变化,功能类似 |
| mounted | onMounted | 名称变化,功能类似 |
| beforeUpdate | onBeforeUpdate | 名称变化,功能类似 |
| updated | onUpdated | 名称变化,功能类似 |
| beforeDestroy | onBeforeUnmount | 名称变化,功能类似 |
| destroyed | onUnmounted | 名称变化,功能类似 |
| activated | onActivated | 名称变化,功能类似 |
| deactivated | onDeactivated | 名称变化,功能类似 |
| errorCaptured | onErrorCaptured | 名称变化,功能类似 |
| - | onRenderTracked | Vue 3新增,开发模式下使用 |
| - | onRenderTriggered | Vue 3新增,开发模式下使用 |
2. 生命周期钩子的执行顺序是什么?
Vue 2的生命周期钩子执行顺序:
- beforeCreate
- created
- beforeMount
- mounted
- beforeUpdate
- updated
- beforeDestroy
- destroyed
Vue 3的生命周期钩子执行顺序:
- setup
- onBeforeMount
- onMounted
- onBeforeUpdate
- onUpdated
- onBeforeUnmount
- onUnmounted
3. 哪些生命周期钩子适合发起API请求?
- Vue 2:
created钩子适合发起API请求,此时实例已经创建完成,可以访问数据和方法。 - Vue 3:
onMounted钩子适合发起API请求,或者在setup函数中使用异步操作。
4. 哪些生命周期钩子适合执行DOM操作?
- Vue 2:
mounted钩子适合执行DOM操作,此时组件已经挂载到DOM上,可以访问DOM元素。 - Vue 3:
onMounted钩子适合执行DOM操作,此时组件已经挂载到DOM上,可以访问DOM元素。
5. 哪些生命周期钩子适合清理资源?
- Vue 2:
beforeDestroy钩子适合清理资源,此时实例仍然可用,可以访问数据和方法。 - Vue 3:
onBeforeUnmount钩子适合清理资源,此时组件仍然可用,可以访问数据和方法。
6. 为什么在created钩子中可以访问数据但不能访问DOM?
在 created 钩子中,Vue实例已经创建完成,数据观测和事件配置已经完成,所以可以访问数据和方法。但此时挂载阶段还未开始,$el 属性还未被创建,所以不能访问DOM。
7. 为什么在mounted钩子中可以访问DOM?
在 mounted 钩子中,组件已经被挂载到DOM上,$el 属性已经被创建并指向挂载的DOM元素,所以可以访问DOM。
8. 如何在Vue 3中使用生命周期钩子?
在Vue 3中,生命周期钩子需要从 vue 中导入,然后在 setup 函数中使用:
import { onMounted, onBeforeUnmount } from 'vue';
export default {
setup() {
onMounted(() => {
console.log('Component mounted');
});
onBeforeUnmount(() => {
console.log('Component before unmount');
});
}
};9. 什么是组合式API的setup函数?它在生命周期中的位置是什么?
setup 函数是Vue 3组合式API的入口点,它在组件创建之前执行,替代了Vue 2的 beforeCreate 和 created 钩子。在 setup 函数中,你可以:
- 初始化响应式数据。
- 定义方法和计算属性。
- 注册生命周期钩子。
- 返回需要暴露给模板的变量和方法。
10. 如何避免生命周期钩子中的内存泄漏?
避免生命周期钩子中的内存泄漏的方法:
- 清理定时器:在
beforeDestroy(Vue 2) 或onBeforeUnmount(Vue 3) 中使用clearInterval或clearTimeout清理定时器。 - 移除事件监听器:在
beforeDestroy(Vue 2) 或onBeforeUnmount(Vue 3) 中使用removeEventListener移除事件监听器。 - 取消API请求:在
beforeDestroy(Vue 2) 或onBeforeUnmount(Vue 3) 中取消未完成的API请求。 - 销毁第三方库实例:在
beforeDestroy(Vue 2) 或onBeforeUnmount(Vue 3) 中销毁第三方库实例。
总结
Vue的生命周期钩子是Vue框架提供的强大功能,允许开发者在组件的不同阶段执行自定义的逻辑。通过理解和正确使用生命周期钩子,开发者可以更好地控制组件的行为,提高应用的性能和可靠性。
Vue 2和Vue 3的生命周期钩子有所不同,Vue 3使用了组合式API,钩子函数的名称和使用方式发生了变化,但核心功能类似。开发者需要根据使用的Vue版本选择合适的生命周期钩子。
在实际开发中,开发者应该根据具体的使用场景选择合适的生命周期钩子,并遵循最佳实践,避免常见的错误,如在生命周期钩子中执行耗时操作、修改数据导致无限循环等。
通过合理使用生命周期钩子,开发者可以构建出更加健壮、高效的Vue应用。