组件生命周期是指Vue组件从创建、挂载、更新到销毁的整个过程。在每个阶段,Vue都会调用特定的生命周期钩子函数,允许我们在特定时机执行自定义代码。
beforeCreate
created
阶段1beforeMount
mounted
阶段2beforeUpdate
updated
阶段3beforeDestroy
destroyed
阶段4以下是完整的生命周期钩子执行顺序:
实例初始化之后,数据观测和事件配置之前调用
初始化阶段实例创建完成,数据观测和事件配置已建立
初始化阶段挂载开始之前调用,模板编译完成,但尚未挂载到DOM
挂载阶段实例已挂载到DOM,可以访问DOM元素
挂载阶段数据更新时调用,DOM尚未重新渲染
更新阶段数据更新后调用,DOM已完成重新渲染
更新阶段实例销毁之前调用,此时实例仍然完全可用
销毁阶段实例销毁后调用,所有指令已解绑,事件监听器已移除
销毁阶段| 钩子函数 | 阶段 | 调用时机 | 典型用途 |
|---|---|---|---|
beforeCreate |
初始化 | 实例初始化之后,数据观测之前 | 插件初始化、全局配置 |
created |
初始化 | 实例创建完成,数据观测已建立 | API数据请求、事件监听 |
beforeMount |
挂载 | 模板编译完成,挂载到DOM之前 | 最后的初始化工作 |
mounted |
挂载 | 实例已挂载到DOM | DOM操作、第三方库初始化 |
beforeUpdate |
更新 | 数据更新时,DOM重新渲染之前 | 获取更新前的DOM状态 |
updated |
更新 | 数据更新后,DOM已完成重新渲染 | 依赖DOM的第三方库更新 |
beforeDestroy |
销毁 | 实例销毁之前 | 清理定时器、取消事件监听 |
destroyed |
销毁 | 实例销毁之后 | 清理引用、通知其他组件 |
在实例初始化之后,数据观测和事件配置之前调用。
export default {
name: 'LifecycleDemo',
beforeCreate() {
console.log('=== beforeCreate ===');
console.log('数据观测还未建立');
console.log('$data:', this.$data); // undefined
console.log('$el:', this.$el); // undefined
console.log('methods:', this.myMethod); // undefined
// 无法访问组件数据和方法
// console.log(this.message); // 会报错
// 可以访问组件选项
console.log('组件名:', this.$options.name);
// 通常用于插件初始化
this.initPlugin();
},
methods: {
initPlugin() {
console.log('初始化插件');
}
}
};
实例创建完成后调用,此时数据观测和事件配置已完成。
export default {
name: 'LifecycleDemo',
data() {
return {
message: 'Hello Vue!',
items: [],
loading: false
};
},
computed: {
reversedMessage() {
return this.message.split('').reverse().join('');
}
},
created() {
console.log('=== created ===');
console.log('数据观测已建立');
console.log('$data:', this.$data); // 可以访问
console.log('message:', this.message); // 'Hello Vue!'
console.log('computed:', this.reversedMessage); // '!euV olleH'
// DOM元素仍然不可访问
console.log('$el:', this.$el); // undefined
// 最常见的用途:发起API请求
this.fetchData();
// 设置事件监听
window.addEventListener('resize', this.handleResize);
// 初始化定时器
this.timer = setInterval(() => {
console.log('定时器执行');
}, 1000);
},
methods: {
fetchData() {
this.loading = true;
// 模拟API请求
setTimeout(() => {
this.items = [
{ id: 1, name: 'Vue.js' },
{ id: 2, name: 'React' },
{ id: 3, name: 'Angular' }
];
this.loading = false;
}, 1500);
},
handleResize() {
console.log('窗口大小改变');
}
}
};
在挂载开始之前调用,此时模板编译已完成,但尚未挂载到DOM。
export default {
name: 'LifecycleDemo',
data() {
return {
message: '准备挂载'
};
},
beforeMount() {
console.log('=== beforeMount ===');
console.log('模板编译已完成');
console.log('$el:', this.$el); // 仍然指向挂载点元素,不是组件内容
// 可以访问数据
console.log('message:', this.message); // '准备挂载'
// 无法访问渲染后的DOM
// console.log(document.querySelector('.content')); // null
// 适合执行挂载前的最后配置
this.prepareForMount();
},
methods: {
prepareForMount() {
console.log('准备挂载...');
// 可以在这里修改数据,但不会触发额外的重新渲染
this.message = '即将挂载';
}
},
template: `
<div class="content">
{{ message }}
</div>
`
};
实例已挂载到DOM,可以访问渲染后的DOM元素。
export default {
name: 'LifecycleDemo',
data() {
return {
message: '已挂载',
width: 0,
height: 0
};
},
mounted() {
console.log('=== mounted ===');
console.log('实例已挂载到DOM');
console.log('$el:', this.$el); // 渲染后的DOM元素
console.log('组件内容:', this.$el.innerHTML);
// 可以访问DOM元素
const contentEl = this.$el.querySelector('.content');
console.log('内容元素:', contentEl);
// 获取DOM尺寸
this.width = this.$el.offsetWidth;
this.height = this.$el.offsetHeight;
console.log('组件尺寸:', this.width, 'x', this.height);
// 初始化第三方库(如图表库、地图等)
this.initChart();
this.initMap();
// 添加DOM事件监听
this.$el.addEventListener('click', this.handleClick);
// 设置焦点
if (this.$refs.input) {
this.$refs.input.focus();
}
},
methods: {
initChart() {
// 初始化图表库,如ECharts、Chart.js等
console.log('初始化图表库');
// this.chart = echarts.init(this.$el.querySelector('.chart-container'));
},
initMap() {
// 初始化地图库
console.log('初始化地图');
},
handleClick(event) {
console.log('组件被点击', event.target);
}
},
template: `
<div class="lifecycle-demo">
<div class="content">{{ message }}</div>
<div class="chart-container" ref="chart"></div>
<input ref="input" type="text" placeholder="获得焦点">
<p>宽度: {{ width }}px, 高度: {{ height }}px</p>
</div>
`
};
mounted钩子中可以进行DOM操作,但应避免直接操作其他组件的DOMmounted中计算mounted中进行mounted中进行数据验证数据更新时调用,此时DOM尚未重新渲染。
export default {
name: 'LifecycleDemo',
data() {
return {
count: 0,
previousCount: 0,
updateLog: []
};
},
beforeUpdate() {
console.log('=== beforeUpdate ===');
console.log('数据已更新,DOM尚未重新渲染');
console.log('count:', this.count);
// 保存更新前的状态
this.previousCount = this.count - 1;
// 获取更新前的DOM信息
const countElement = this.$el.querySelector('.count-value');
if (countElement) {
console.log('DOM中的旧值:', countElement.textContent);
}
// 记录更新日志
this.updateLog.push({
timestamp: new Date().toISOString(),
from: this.previousCount,
to: this.count,
phase: 'beforeUpdate'
});
// 可以在这里取消不必要的更新
if (this.count > 100) {
console.warn('计数超过限制,取消更新');
// 注意:不能在这里直接修改触发更新的数据
// 否则会导致无限循环
}
},
methods: {
increment() {
this.count++;
},
decrement() {
this.count--;
}
},
template: `
<div>
<button @click="decrement">-</button>
<span class="count-value">{{ count }}</span>
<button @click="increment">+</button>
<div>更新日志: {{ updateLog.length }} 条</div>
</div>
`
};
数据更新后调用,此时DOM已完成重新渲染。
export default {
name: 'LifecycleDemo',
data() {
return {
count: 0,
width: 0,
chartData: [],
chartInstance: null
};
},
updated() {
console.log('=== updated ===');
console.log('DOM已重新渲染');
console.log('count:', this.count);
// 获取更新后的DOM信息
const countElement = this.$el.querySelector('.count-value');
if (countElement) {
console.log('DOM中的新值:', countElement.textContent);
}
// 更新组件尺寸
this.updateDimensions();
// 更新图表数据
this.updateChart();
// 滚动到最新内容
this.scrollToBottom();
// 注意:不要在这里修改触发更新的数据,否则会导致无限循环
// ❌ 错误示例:this.count = this.count + 1;
// 可以使用nextTick确保DOM更新完成
this.$nextTick(() => {
console.log('DOM更新确认完成');
this.performPostUpdateOperations();
});
},
methods: {
updateDimensions() {
this.width = this.$el.offsetWidth;
console.log('更新后宽度:', this.width);
},
updateChart() {
if (this.chartInstance) {
// 更新图表数据
this.chartInstance.setOption({
series: [{
data: this.chartData
}]
});
}
},
scrollToBottom() {
const container = this.$el.querySelector('.log-container');
if (container) {
container.scrollTop = container.scrollHeight;
}
},
performPostUpdateOperations() {
// DOM更新后的操作
console.log('执行更新后操作');
}
}
};
updated钩子中修改触发更新的数据,否则会导致无限循环updated时要格外小心,确保不会引起额外的重新渲染this.$nextTick()确保DOM更新完成watch或计算属性更合适实例销毁之前调用,此时实例仍然完全可用。
export default {
name: 'LifecycleDemo',
data() {
return {
timer: null,
eventListeners: [],
subscriptions: []
};
},
created() {
// 创建定时器
this.timer = setInterval(() => {
console.log('定时器运行中...');
}, 1000);
// 添加事件监听
window.addEventListener('resize', this.handleResize);
this.eventListeners.push(['resize', this.handleResize]);
// 创建WebSocket连接
this.connectWebSocket();
},
beforeDestroy() {
console.log('=== beforeDestroy ===');
console.log('实例即将销毁,清理资源');
// 1. 清理定时器
if (this.timer) {
clearInterval(this.timer);
this.timer = null;
console.log('定时器已清理');
}
// 2. 移除事件监听
this.eventListeners.forEach(([event, handler]) => {
window.removeEventListener(event, handler);
});
this.eventListeners = [];
console.log('事件监听已移除');
// 3. 取消API请求
if (this.requestController) {
this.requestController.abort();
console.log('API请求已取消');
}
// 4. 断开WebSocket连接
this.disconnectWebSocket();
// 5. 清理第三方库实例
if (this.chartInstance) {
this.chartInstance.dispose();
this.chartInstance = null;
console.log('图表实例已清理');
}
// 6. 取消订阅
this.subscriptions.forEach(subscription => {
subscription.unsubscribe();
});
this.subscriptions = [];
// 7. 清理DOM引用
this.$refs = {};
// 8. 通知父组件或其他组件
this.$emit('component-will-destroy', this.componentId);
// 可以访问数据和方法的最后机会
console.log('最后的数据:', this.$data);
this.saveState();
},
methods: {
handleResize() {
console.log('窗口大小改变');
},
connectWebSocket() {
// 模拟WebSocket连接
console.log('WebSocket连接已建立');
},
disconnectWebSocket() {
console.log('WebSocket连接已断开');
},
saveState() {
// 保存组件状态到localStorage
localStorage.setItem('component-state', JSON.stringify(this.$data));
}
}
};
实例销毁后调用,所有指令已解绑,事件监听器已移除。
export default {
name: 'LifecycleDemo',
data() {
return {
componentId: 'demo-' + Date.now(),
isDestroyed: false
};
},
destroyed() {
console.log('=== destroyed ===');
console.log('实例已完全销毁');
// 此时组件已不可用
console.log('$el:', this.$el); // null
console.log('$data:', this.$data); // 仍然可以访问,但不应该再使用
// 标记组件已销毁
this.isDestroyed = true;
// 清理全局引用
const globalKey = `component_${this.componentId}`;
if (window[globalKey]) {
delete window[globalKey];
}
// 通知全局事件总线
if (this.$root.eventBus) {
this.$root.eventBus.$emit('component-destroyed', this.componentId);
}
// 清理可能的内存泄漏
this.cleanupMemoryLeaks();
// 记录销毁日志
this.logDestruction();
// 注意:此时不能再调用组件的方法或访问DOM
// ❌ 错误示例:this.$el.querySelector('...');
// ❌ 错误示例:this.someMethod();
},
methods: {
cleanupMemoryLeaks() {
// 清理可能的内存泄漏
console.log('清理潜在的内存泄漏');
// 清理闭包中的引用
this.callbacks = null;
this.handlers = null;
// 清理大对象
this.largeData = null;
this.buffers = null;
},
logDestruction() {
// 记录销毁信息(可以发送到服务器)
const logEntry = {
component: this.$options.name,
id: this.componentId,
destroyedAt: new Date().toISOString(),
duration: Date.now() - this.createdAt
};
console.log('销毁日志:', logEntry);
// 在实际应用中,可以发送到服务器
// this.$http.post('/api/logs/destruction', logEntry);
}
},
created() {
this.createdAt = Date.now();
}
};
一个综合运用生命周期钩子的用户列表组件:
// UserList.vue - 完整的生命周期示例
export default {
name: 'UserList',
props: {
apiUrl: {
type: String,
required: true
},
autoRefresh: {
type: Boolean,
default: true
},
pageSize: {
type: Number,
default: 10
}
},
data() {
return {
users: [],
loading: false,
error: null,
page: 1,
totalPages: 1,
refreshTimer: null,
scrollListener: null,
resizeObserver: null,
isOnline: navigator.onLine
};
},
computed: {
visibleUsers() {
return this.users.slice(0, this.page * this.pageSize);
},
hasMoreUsers() {
return this.page < this.totalPages;
}
},
// 1. 初始化阶段
beforeCreate() {
console.log('UserList: beforeCreate - 组件初始化开始');
// 此时无法访问数据和方法
},
created() {
console.log('UserList: created - 组件实例已创建');
// 设置初始状态
this.loading = true;
// 发起初始数据请求
this.fetchUsers();
// 监听网络状态
window.addEventListener('online', this.handleOnline);
window.addEventListener('offline', this.handleOffline);
// 初始化WebSocket连接(如果需要实时更新)
this.initWebSocket();
// 设置自动刷新
if (this.autoRefresh) {
this.setupAutoRefresh();
}
},
// 2. 挂载阶段
beforeMount() {
console.log('UserList: beforeMount - 即将挂载到DOM');
// 可以在这里执行挂载前的最后准备
},
mounted() {
console.log('UserList: mounted - 组件已挂载到DOM');
// 可以访问DOM元素
this.setupInfiniteScroll();
this.setupResizeObserver();
// 初始化第三方库(如虚拟滚动)
this.initVirtualScroll();
// 设置焦点
if (this.$refs.searchInput) {
this.$refs.searchInput.focus();
}
// 记录组件挂载时间(用于性能监控)
this.mountedAt = Date.now();
this.reportPerformance();
},
// 3. 更新阶段
beforeUpdate() {
console.log('UserList: beforeUpdate - 数据即将更新,DOM未渲染');
// 可以在这里保存更新前的状态
this.previousUserCount = this.users.length;
},
updated() {
console.log('UserList: updated - 数据已更新,DOM已重新渲染');
// 更新虚拟滚动
this.updateVirtualScroll();
// 滚动到新添加的用户
this.scrollToNewUsers();
// 使用nextTick确保DOM更新完成
this.$nextTick(() => {
this.performPostUpdateChecks();
});
},
// 4. 销毁阶段
beforeDestroy() {
console.log('UserList: beforeDestroy - 组件即将销毁,清理资源');
// 清理定时器
if (this.refreshTimer) {
clearInterval(this.refreshTimer);
this.refreshTimer = null;
}
// 移除事件监听
window.removeEventListener('online', this.handleOnline);
window.removeEventListener('offline', this.handleOffline);
if (this.scrollListener) {
window.removeEventListener('scroll', this.scrollListener);
}
// 断开WebSocket连接
this.disconnectWebSocket();
// 清理第三方库
this.cleanupVirtualScroll();
if (this.resizeObserver) {
this.resizeObserver.disconnect();
}
// 取消API请求
if (this.currentRequest) {
this.currentRequest.abort();
}
// 保存组件状态
this.saveComponentState();
},
destroyed() {
console.log('UserList: destroyed - 组件已销毁');
// 清理全局引用
this.cleanupGlobalReferences();
// 记录销毁日志
this.logComponentLifetime();
},
methods: {
// 数据获取方法
async fetchUsers() {
try {
this.loading = true;
this.error = null;
// 使用AbortController取消请求
this.currentRequest = new AbortController();
const response = await fetch(`${this.apiUrl}?page=${this.page}`, {
signal: this.currentRequest.signal
});
const data = await response.json();
this.users = data.users;
this.totalPages = data.totalPages;
this.page = data.currentPage;
} catch (error) {
if (error.name !== 'AbortError') {
this.error = error.message;
console.error('获取用户数据失败:', error);
}
} finally {
this.loading = false;
this.currentRequest = null;
}
},
// 自动刷新设置
setupAutoRefresh() {
this.refreshTimer = setInterval(() => {
console.log('自动刷新用户数据');
this.fetchUsers();
}, 30000); // 每30秒刷新一次
},
// 无限滚动
setupInfiniteScroll() {
this.scrollListener = () => {
const scrollTop = window.scrollY;
const windowHeight = window.innerHeight;
const documentHeight = document.documentElement.scrollHeight;
if (scrollTop + windowHeight >= documentHeight - 100) {
this.loadMore();
}
};
window.addEventListener('scroll', this.scrollListener);
},
// 虚拟滚动初始化
initVirtualScroll() {
// 这里可以初始化虚拟滚动库
console.log('初始化虚拟滚动');
// this.virtualScroll = new VirtualScroll(this.$el, {
// itemHeight: 50,
// renderItem: this.renderUserItem
// });
},
// 网络状态处理
handleOnline() {
this.isOnline = true;
console.log('网络恢复,重新加载数据');
this.fetchUsers();
},
handleOffline() {
this.isOnline = false;
console.log('网络断开,显示离线数据');
},
// 加载更多
loadMore() {
if (this.hasMoreUsers && !this.loading) {
this.page++;
this.fetchUsers();
}
},
// 性能报告
reportPerformance() {
const loadTime = Date.now() - this.mountedAt;
console.log(`组件加载时间: ${loadTime}ms`);
// 在实际应用中,可以发送到性能监控系统
// this.$gtag('timing', 'component_load', loadTime);
},
// 保存组件状态
saveComponentState() {
const state = {
page: this.page,
users: this.users.slice(0, 20) // 只保存前20个用户
};
localStorage.setItem('userList_state', JSON.stringify(state));
},
// 清理全局引用
cleanupGlobalReferences() {
// 清理可能的内存泄漏
this.users = null;
this.callbacks = null;
// 从全局事件总线移除监听
if (this.$root.eventBus) {
this.$root.eventBus.$off('user-updated', this.handleUserUpdated);
}
},
// 记录组件生命周期
logComponentLifetime() {
const lifetime = Date.now() - this.mountedAt;
console.log(`组件存活时间: ${lifetime}ms`);
// 发送销毁日志到服务器
const logData = {
component: 'UserList',
lifetime: lifetime,
userCount: this.previousUserCount || 0,
destroyedAt: new Date().toISOString()
};
// 在实际应用中,可以发送到日志系统
// this.$http.post('/api/logs/component-lifecycle', logData);
}
},
// 模板
template: `
<div class="user-list" ref="container">
<div class="user-list-header">
<h3>用户列表</h3>
<input
ref="searchInput"
type="text"
placeholder="搜索用户..."
class="search-input"
>
<span class="status-badge" :class="isOnline ? 'online' : 'offline'">
{{ isOnline ? '在线' : '离线' }}
</span>
</div>
<div v-if="loading && users.length === 0" class="loading">
加载中...
</div>
<div v-else-if="error" class="error">
{{ error }}
<button @click="fetchUsers">重试</button>
</div>
<div v-else class="user-items">
<div
v-for="user in visibleUsers"
:key="user.id"
class="user-item"
>
<img :src="user.avatar" :alt="user.name">
<div class="user-info">
<h4>{{ user.name }}</h4>
<p>{{ user.email }}</p>
</div>
</div>
</div>
<div v-if="loading && users.length > 0" class="loading-more">
正在加载更多...
</div>
<div v-if="hasMoreUsers && !loading" class="load-more">
<button @click="loadMore">加载更多</button>
</div>
<div class="user-list-footer">
共 {{ users.length }} 个用户,显示 {{ visibleUsers.length }} 个
</div>
</div>
`
};
beforeDestroy中清理updated中修改触发更新的数据$nextTick确保DOM更新完成debouncemounted中初始化第三方库现象:在updated中修改数据导致死循环
解决:使用条件判断或计算属性
现象:组件销毁后仍有引用未清理
解决:在beforeDestroy中系统清理
现象:在created中访问$el
解决:在mounted中进行DOM操作
现象:异步回调在组件销毁后执行
解决:使用标志位或清理异步操作
分析以下组件代码,指出生命周期钩子的使用问题:
// 有问题的组件代码
export default {
name: 'ProblematicComponent',
data() {
return {
timer: null,
data: [],
chart: null
};
},
created() {
// 问题1:DOM操作
document.querySelector('.some-element').style.color = 'red';
// 问题2:创建定时器但未清理
this.timer = setInterval(() => {
this.fetchData();
}, 1000);
},
mounted() {
// 问题3:在mounted中重复created中的操作
this.fetchData();
// 问题4:初始化图表但未销毁
this.chart = echarts.init(this.$el);
// 问题5:添加事件监听但未移除
window.addEventListener('scroll', () => {
console.log('滚动');
});
},
updated() {
// 问题6:在updated中修改触发更新的数据
if (this.data.length > 100) {
this.data = this.data.slice(0, 50);
}
// 问题7:直接操作DOM
this.$el.style.backgroundColor = 'yellow';
},
methods: {
fetchData() {
// 模拟API请求
setTimeout(() => {
this.data.push({ id: Date.now() });
}, 500);
}
}
// 问题8:缺少beforeDestroy/destroyed钩子
};