Vue 生命周期详解
一、Vue 生命周期概述
Vue.js 作为当前最流行的前端框架之一,其核心特性之一就是完善的生命周期管理机制。Vue 生命周期指的是 Vue 实例从创建、挂载、更新到销毁的完整过程,在这个过程中,Vue 提供了一系列的钩子函数(Hook Functions),允许开发者在特定阶段插入自定义代码逻辑 。
生命周期的重要性
- 性能优化:合理利用生命周期钩子可以优化组件性能
- 资源管理:在适当时机进行资源初始化和清理
- 数据管理:控制数据的初始化和更新时机
- DOM 操作:在正确阶段进行 DOM 操作
二、Vue 2 生命周期完整流程
生命周期阶段划分
| 阶段 | 钩子函数 | 执行时机 | 主要用途 |
|---|---|---|---|
| 创建阶段 | beforeCreate | 实例初始化后,数据观测之前 | 初始化非响应式变量 |
| 创建阶段 | created | 实例创建完成,数据观测已配置 | 数据初始化、异步请求 |
| 挂载阶段 | beforeMount | 挂载开始之前,模板编译完成 | 最后的数据修改机会 |
| 挂载阶段 | mounted | 实例挂载到 DOM 后 | DOM 操作、第三方库初始化 |
| 更新阶段 | beforeUpdate | 数据更新时,虚拟 DOM 重新渲染前 | 获取更新前的状态 |
| 更新阶段 | updated | 数据更新后,虚拟 DOM 重新渲染后 | 操作更新后的 DOM |
| 销毁阶段 | beforeDestroy | 实例销毁之前 | 清理定时器、取消事件监听 |
| 销毁阶段 | destroyed | 实例销毁后 | 清理内存、解除绑定 |
详细钩子函数解析
- beforeCreate
javascript
export default {
beforeCreate() {
// 此时无法访问 data、computed、methods 等
console.log('beforeCreate 钩子被调用');
console.log('data:', this.message); // undefined
console.log('methods:', this.getData); // undefined
},
data() {
return {
message: 'Hello Vue'
};
},
methods: {
getData() {
return this.message;
}
}
}
此阶段实例刚被创建,数据观测和事件配置都还未完成 。
- created
javascript
export default {
data() {
return {
userList: [],
loading: false
};
},
created() {
// 可以访问 data、computed、methods 等
console.log('created 钩子被调用');
console.log('data:', this.userList); // []
console.log('methods:', this.fetchUsers); // function
// 常见应用:发起异步请求
this.loading = true;
this.fetchUsers();
},
methods: {
async fetchUsers() {
try {
const response = await axios.get('/api/users');
this.userList = response.data;
} catch (error) {
console.error('获取用户列表失败:', error);
} finally {
this.loading = false;
}
}
}
}
此时实例已完成数据观测,可以访问响应式数据和方法,但尚未挂载到 DOM 。
- beforeMount
javascript
export default {
data() {
return {
count: 0
};
},
beforeMount() {
// 模板已编译,但尚未挂载到 DOM
console.log('beforeMount 钩子被调用');
console.log('DOM 元素:', this.$el); // undefined
// 可以修改数据,但不会触发更新
this.count = 10;
}
}
此阶段完成了模板编译,但尚未将编译结果挂载到页面中 。
- mounted
javascript
export default {
data() {
return {
chart: null
};
},
mounted() {
// 实例已挂载到 DOM,可以访问 $el
console.log('mounted 钩子被调用');
console.log('DOM 元素:', this.$el); // 真实的 DOM 元素
// 常见应用:初始化第三方库
this.chart = new Chart(this.$el.querySelector('#chart'), {
type: 'line',
data: {
labels: ['Jan', 'Feb', 'Mar'],
datasets: [{
label: 'Sales',
data: [65, 59, 80]
}]
}
});
// 添加事件监听器
window.addEventListener('resize', this.handleResize);
},
beforeDestroy() {
// 清理工作
if (this.chart) {
this.chart.destroy();
}
window.removeEventListener('resize', this.handleResize);
},
methods: {
handleResize() {
if (this.chart) {
this.chart.resize();
}
}
}
}
此阶段实例已挂载到 DOM,可以进行 DOM 操作和第三方库初始化 。
- beforeUpdate
javascript
export default {
data() {
return {
items: ['A', 'B', 'C'],
searchText: ''
};
},
computed: {
filteredItems() {
return this.items.filter(item =>
item.includes(this.searchText)
);
}
},
beforeUpdate() {
// 数据变化时,DOM 更新前调用
console.log('beforeUpdate 钩子被调用');
console.log('当前搜索文本:', this.searchText);
console.log('当前 DOM 内容:', this.$el.innerHTML);
// 可以获取更新前的状态
const currentScrollPosition = this.$el.scrollTop;
this.previousScroll = currentScrollPosition;
}
}
此阶段在响应式数据更新时调用,适合在 DOM 重新渲染前访问现有 DOM 状态 。
- updated
javascript
export default {
data() {
return {
messages: [],
autoScroll: true
};
},
updated() {
// DOM 已更新完成
console.log('updated 钩子被调用');
// 常见应用:基于新内容调整 UI
if (this.autoScroll) {
const container = this.$el.querySelector('.message-container');
container.scrollTop = container.scrollHeight;
}
// 注意:避免在 updated 中修改状态,可能导致无限循环
// this.messages.push('新消息'); // 危险操作!
}
}
此阶段在数据更新导致的虚拟 DOM 重新渲染和打补丁后调用 。
- beforeDestroy
javascript
export default {
data() {
return {
timer: null,
websocket: null
};
},
created() {
// 创建时设置定时器
this.timer = setInterval(() => {
console.log('定时器执行中...');
}, 1000);
// 创建 WebSocket 连接
this.websocket = new WebSocket('ws://localhost:8080');
},
beforeDestroy() {
// 实例销毁前进行清理
console.log('beforeDestroy 钩子被调用');
// 清除定时器
if (this.timer) {
clearInterval(this.timer);
this.timer = null;
}
// 关闭 WebSocket 连接
if (this.websocket) {
this.websocket.close();
}
// 移除事件监听器
window.removeEventListener('keydown', this.handleKeydown);
}
}
此阶段实例仍然完全可用,适合进行资源清理工作 。
- destroyed
javascript
export default {
destroyed() {
// 实例已销毁
console.log('destroyed 钩子被调用');
// 此时所有的事件监听器已被移除
// 所有的子实例也已被销毁
// 可以进行最终的内存清理
this.cleanupMemory();
},
methods: {
cleanupMemory() {
// 清理大型数据对象
this.largeData = null;
}
}
}
此阶段实例已完全销毁,所有的事件监听器和子实例都已被移除 。
三、父子组件生命周期执行顺序
加载阶段执行顺序
javascript
// 父组件
export default {
beforeCreate() { console.log('父组件 beforeCreate'); },
created() { console.log('父组件 created'); },
beforeMount() { console.log('父组件 beforeMount'); },
mounted() { console.log('父组件 mounted'); }
}
// 子组件
export default {
beforeCreate() { console.log('子组件 beforeCreate'); },
created() { console.log('子组件 created'); },
beforeMount() { console.log('子组件 beforeMount'); },
mounted() { console.log('子组件 mounted'); }
}
// 执行顺序:
// 父组件 beforeCreate
// 父组件 created
// 父组件 beforeMount
// 子组件 beforeCreate
// 子组件 created
// 子组件 beforeMount
// 子组件 mounted
// 父组件 mounted
父组件的挂载过程会在所有子组件都挂载完成后才完成 。
更新阶段执行顺序
javascript
// 当父组件数据变化时
// 父组件 beforeUpdate
// 子组件 beforeUpdate
// 子组件 updated
// 父组件 updated
更新阶段从父组件开始,然后到子组件,最后回到父组件 。
销毁阶段执行顺序
javascript
// 父组件 beforeDestroy
// 子组件 beforeDestroy
// 子组件 destroyed
// 父组件 destroyed
销毁阶段先销毁子组件,再完成父组件的销毁 。
四、特殊场景的生命周期
keep-alive 组件的生命周期
javascript
export default {
// 常规生命周期
created() {
console.log('组件 created');
},
mounted() {
console.log('组件 mounted');
},
// keep-alive 特有生命周期
activated() {
// 组件被激活时调用(从缓存中恢复)
console.log('组件 activated');
this.resumeActivity();
},
deactivated() {
// 组件被停用时调用(进入缓存)
console.log('组件 deactivated');
this.pauseActivity();
},
methods: {
resumeActivity() {
// 恢复定时器、重新连接等
this.startPolling();
},
pauseActivity() {
// 暂停耗时操作,节省资源
this.stopPolling();
}
}
}
使用 keep-alive 缓存的组件会有额外的 activated 和 deactivated 钩子 。
动态组件的生命周期
javascript
export default {
data() {
return {
currentComponent: 'ComponentA'
};
},
methods: {
switchComponent() {
this.currentComponent =
this.currentComponent === 'ComponentA' ? 'ComponentB' : 'ComponentA';
}
},
components: {
ComponentA: {
created() { console.log('ComponentA created'); },
destroyed() { console.log('ComponentA destroyed'); }
},
ComponentB: {
created() { console.log('ComponentB created'); },
destroyed() { console.log('ComponentB destroyed'); }
}
}
}
动态组件切换时会触发相应组件的创建和销毁生命周期 。
五、生命周期最佳实践
1. 数据初始化最佳时机
javascript
export default {
data() {
return {
user: null,
posts: [],
loading: false
};
},
async created() {
// created 是发起异步请求的最佳时机
await this.initializeData();
},
methods: {
async initializeData() {
this.loading = true;
try {
// 并行发起多个请求
const [userResponse, postsResponse] = await Promise.all([
this.$http.get('/api/user'),
this.$http.get('/api/posts')
]);
this.user = userResponse.data;
this.posts = postsResponse.data;
} catch (error) {
console.error('数据初始化失败:', error);
this.$message.error('加载失败');
} finally {
this.loading = false;
}
}
}
}
2. DOM 操作的正确时机
javascript
export default {
data() {
return {
chart: null,
map: null
};
},
mounted() {
// mounted 是进行 DOM 操作的安全时机
this.initializeThirdPartyLibs();
},
methods: {
initializeThirdPartyLibs() {
// 初始化图表库
this.chart = new Chart(this.$el.querySelector('#chart'), config);
// 初始化地图
this.map = new Map(this.$el.querySelector('#map'), mapConfig);
// 绑定自定义事件
this.bindCustomEvents();
},
beforeDestroy() {
// 妥善清理第三方库资源
if (this.chart) {
this.chart.destroy();
}
if (this.map) {
this.map.remove();
}
}
}
}
3. 性能优化相关实践
javascript
export default {
data() {
return {
heavyData: [],
eventHandlers: []
};
},
created() {
// 避免在 created 中执行重计算
this.initializeLightweightData();
},
mounted() {
// 在 mounted 中执行较重的操作
this.loadHeavyData();
},
beforeDestroy() {
// 彻底清理,防止内存泄漏
this.cleanup();
},
methods: {
initializeLightweightData() {
// 轻量级数据初始化
this.userPreferences = this.loadPreferences();
},
async loadHeavyData() {
// 重量级数据加载
this.heavyData = await this.$http.get('/api/large-dataset');
},
cleanup() {
// 清理所有事件监听器
this.eventHandlers.forEach(handler => {
window.removeEventListener(handler.type, handler.listener);
});
this.eventHandlers = [];
// 清理大型数据
this.heavyData = null;
}
}
}
六、常见问题与解决方案
1. 异步操作与生命周期
javascript
export default {
data() {
return {
data: null,
loading: false
};
},
async created() {
this.loading = true;
try {
// 在 created 中发起请求
this.data = await this.fetchData();
} catch (error) {
this.handleError(error);
} finally {
this.loading = false;
}
},
methods: {
async fetchData() {
// 模拟异步请求
return new Promise(resolve => {
setTimeout(() => {
resolve({ message: '数据加载完成' });
}, 1000);
});
},
handleError(error) {
console.error('数据加载失败:', error);
}
}
}
2. 避免内存泄漏
javascript
export default {
data() {
return {
timer: null,
observers: [],
eventListeners: []
};
},
created() {
// 设置定时器
this.timer = setInterval(this.updateData, 5000);
// 创建观察者
const observer = new MutationObserver(this.handleMutation);
observer.observe(document.body, { childList: true });
this.observers.push(observer);
// 添加事件监听
const resizeHandler = this.handleResize.bind(this);
window.addEventListener('resize', resizeHandler);
this.eventListeners.push({ type: 'resize', handler: resizeHandler });
},
beforeDestroy() {
// 清理所有资源
this.cleanupResources();
},
methods: {
cleanupResources() {
// 清除定时器
if (this.timer) {
clearInterval(this.timer);
this.timer = null;
}
// 断开观察者
this.observers.forEach(observer => observer.disconnect());
this.observers = [];
// 移除事件监听
this.eventListeners.forEach(({ type, handler }) => {
window.removeEventListener(type, handler);
});
this.eventListeners = [];
},
updateData() {
// 定时更新数据
console.log('数据更新');
},
handleMutation(mutations) {
// 处理 DOM 变化
},
handleResize() {
// 处理窗口 resize
}
}
}
通过深入理解 Vue 生命周期各个阶段的特性和最佳实践,开发者可以编写出更加健壮、高效和可维护的 Vue 应用程序。生命周期钩子为开发者提供了精确控制组件行为的能力,是 Vue 框架强大功能的重要组成部分 。