生命周期 | 自在学生命周期
每个Vue组件都会经历从创建到销毁的完整生命周期。在这个过程中,Vue会在特定的时机调用一些函数,这些函数就是生命周期钩子。通过生命周期钩子,我们可以在组件的不同阶段执行操作,比如初始化数据、发送请求、清理资源等。
理解生命周期对于开发Vue应用非常重要。它帮助我们理解组件的工作机制,知道在什么时候可以访问DOM,什么时候可以发送请求,什么时候需要清理资源。在这一堂课中,我们将深入学习Vue的生命周期。

什么是生命周期
Vue组件的生命周期可以分为几个阶段:创建、挂载、更新和销毁。每个阶段都有对应的钩子函数,我们可以在这些钩子中执行特定的操作。
让我们通过一个简单的例子来理解生命周期:
<div id="app">
<lifecycle-demo :count="count"></lifecycle-demo>
<button @click="count++">增加计数</button>
</div>
<script>
const { createApp, ref } = Vue;
const LifecycleDemo = {
props: ['count'],
beforeCreate() {
console.log('beforeCreate: 组件实例刚被创建,数据和方法还未初始化');
},
created() {
console.log('created: 组件实例创建完成,数据和方法已初始化,但还未挂载到DOM');
},
beforeMount() {
console.log('beforeMount: 组件挂载到DOM之前');
},
mounted() {
console.log('mounted: 组件已挂载到DOM,可以访问DOM元素');
},
beforeUpdate() {
console.log('beforeUpdate: 组件更新之前');
},
updated() {
console.log('updated: 组件更新完成');
},
beforeUnmount() {
console.log('beforeUnmount: 组件卸载之前');
},
unmounted() {
console.log('unmounted: 组件已卸载');
},
template: `
<div>
<p>计数:{{ count }}</p>
</div>
`
};
const app = createApp({
setup() {
const count = ref(0);
return {
count
};
},
components: {
'lifecycle-demo': LifecycleDemo
}
});
app.mount('#app');
</script>
如果你运行这段代码并观察控制台,你会看到各个生命周期钩子的执行顺序。这帮助我们理解组件在不同阶段的状态。
选项式API的生命周期钩子
在选项式API(Options API)中,我们可以通过在组件对象的选项中直接声明各类生命周期钩子函数(如 beforeCreate、created、beforeMount、mounted、beforeUpdate、updated、beforeUnmount、unmounted 等)。
这些钩子会在组件的不同生命周期阶段自动依次调用,允许我们在组件初始化、DOM 挂载、数据变更、卸载等关键时刻插入自定义逻辑。例如,你可以在 created 钩子里发起数据请求、在 mounted 钩子中操作 DOM、在 beforeUnmount 中做清理工作。
每个生命周期钩子都作为组件对象的一个方法选项直接存在,与 data、methods、computed 等属性并列。这样可以帮助我们精确掌控组件的整个生命周期过程。
<div id="app">
<component-a></component-a>
</div>
<script>
const { createApp } = Vue;
const ComponentA = {
data() {
return {
message: 'Hello Vue'
};
},
beforeCreate() {
- beforeCreate
:组件实例刚被创建,此时this还未创建,无法访问data、methods`等。这个钩子很少使用。
- created
:组件实例创建完成,此时可以访问data、methods`等,但DOM还未创建。这是发送请求、初始化数据的好时机。
- beforeMount`:组件挂载到DOM之前,此时模板已经编译完成,但还未挂载到页面。
- mounted`:组件已挂载到DOM,此时可以访问DOM元素。这是操作DOM、初始化第三方库的好时机。
更新阶段的钩子
当组件的数据(如 data、props 等)发生变化时,Vue 会自动触发一组“更新阶段的生命周期钩子”。这些钩子的执行顺序如下:
- beforeUpdate:在响应式数据发生变化,组件即将重新渲染前调用。此时数据已是最新的,但页面上的 DOM 尚未被更新为新状态。你可以在这里访问 this 获取更新后的数据,但千万不要在这里再次修改数据,否则可能导致无限重渲。
- updated:在组件的 DOM 已经根据最新的数据完成了渲染后调用。这个钩子可以安全地访问已更新的 DOM 元素,通常用于依赖 DOM 结构的第三方库初始化或其他操作。不过,请避免在此处再修改数据,否则会进入死循环。
通过这些钩子,你可以在数据变更、组件重新渲染的各个阶段进行相应的操作,比如:在 DOM 更新前执行某些计算逻辑,在 DOM 更新后与界面进行交互等。
<div id="app">
<counter-component></counter-component>
</div>
<script>
const { createApp } = Vue;
const CounterComponent = {
data() {
return {
count: 0
};
},
beforeUpdate() {
beforeUpdate:数据已更新,但DOM还未更新。这个钩子很少使用。
updated:DOM已更新。可以在这里操作更新后的DOM,但要注意避免在这里修改数据,否则可能导致无限循环。
销毁阶段的钩子
当组件被销毁(卸载)时,会依次触发两个销毁阶段(卸载阶段)的生命周期钩子:beforeUnmount 和 unmounted。
beforeUnmount:组件实例将要被卸载时调用,在这个钩子中通常进行一些清理操作,比如清除定时器、移除事件监听等。此时组件仍然可以访问所有的数据和 DOM。
unmounted:组件实例已经被卸载并销毁时调用,这时你已经无法访问组件实例中的数据和 DOM 节点,常用于一些最终的清理操作或者发送日志等。
这些钩子主要用于组件销毁时的资源管理,避免内存泄漏或副作用残留。
<div id="app">
<button @click="show = !show">切换显示</button>
<component-b v-if="show"></component-b>
</div>
<script>
const { createApp, ref } = Vue;
const ComponentB = {
- beforeUnmount`:组件卸载之前,这是清理资源的好时机,比如清除定时器、取消请求、移除事件监听器等。
- unmounted`:组件已卸载。这个钩子很少使用。
在组件销毁时,一定要清理资源,比如定时器、事件监听器等。否则可能导致内存泄漏。
组合式API的生命周期钩子
在组合式 API 中,生命周期钩子不能像选项式 API 那样直接写作对象方法,而是需要单独从 Vue 中按需导入,然后在 setup 函数内调用对应的钩子函数。例如:
import { onMounted, onUnmounted } from 'vue';
setup() {
onMounted(() => {
// 组件挂载后执行的逻辑
});
onUnmounted(() => {
// 组件卸载时执行的逻辑
});
}
常用的生命周期钩子包括 onBeforeMount、onMounted、onBeforeUpdate、onUpdated、onBeforeUnmount、onUnmounted 等,全部以“on”为前缀,便于区分。
<div id="app">
<component-c></component-c>
</div>
<script>
const { createApp, ref, onBeforeMount, onMounted, onBeforeUpdate, onUpdated, onBeforeUnmount, onUnmounted } = Vue;
const ComponentC =
在组合式API中,生命周期钩子的命名稍有不同:beforeCreate和created被setup替代,其他钩子都加上了on前缀。
注意,组合式API中没有beforeCreate和created钩子,因为setup函数在这些钩子之前执行。如果你需要在组件创建时执行操作,直接在setup中执行即可。
生命周期钩子的使用场景
不同的生命周期钩子适用于不同的场景。让我们看一些实际的使用场景:
在created/mounted中发送请求
<div id="app">
<user-list></user-list>
</div>
<script>
const { createApp, ref } = Vue;
const UserList = {
data() {
return {
users: [],
loading: false
};
在这个例子中,我们在created钩子中发送请求获取用户列表。虽然也可以在mounted中发送请求,但在created中发送可以更早获取数据。
在mounted中操作DOM
<div id="app">
<chart-component></chart-component>
</div>
<script>
const { createApp } = Vue;
const ChartComponent = {
mounted() {
// DOM已挂载,可以初始化图表库
const canvas = this.$el.querySelector('canvas'
在这个例子中,我们在mounted钩子中操作DOM,初始化图表。因为只有在mounted之后,DOM元素才真正存在。
在beforeUnmount中清理资源
<div id="app">
<button @click="show = !show">切换显示</button>
<timer-component v-if="show"></timer-component>
</div>
<script>
const { createApp, ref } = Vue;
const TimerComponent = {
在这个例子中,我们在beforeUnmount钩子中清理定时器。如果不清理,定时器会继续运行,导致内存泄漏。
生命周期图示
让我们通过一个图示来理解完整的生命周期流程:
综合示例
下面我们将通过一个更详细的综合示例,来全面巩固和理解 Vue 组件的生命周期。
在这个示例中,我们会自定义一个组件,并在每个生命周期钩子中进行不同的操作与日志记录,以观察生命周期的完整流程,以及每一步执行的时机和作用。
你可以通过切换、修改 props 等操作,直观地看到各生命周期钩子的调用顺序和效果。
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>生命周期钩子示例</title>
<script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
在这个例子中,我们创建了一个组件,它会在各个生命周期钩子中记录日志。通过观察日志,我们可以清楚地看到组件生命周期的执行顺序。
下一步
在这一部分中,我们学习了Vue的生命周期钩子。我们了解了组件从创建到销毁的完整生命周期,学习了选项式API和组合式API中的生命周期钩子,以及它们的使用场景。
理解生命周期对于开发Vue应用非常重要。它帮助我们理解组件的工作机制,知道在什么时候可以访问DOM,什么时候可以发送请求,什么时候需要清理资源。
在下一堂课,我们将学习组合式API,了解setup函数、组合式函数等高级特性。
// 此时this还未创建,无法访问data、methods等
console.log('beforeCreate');
},
created() {
// 此时可以访问data、methods等,但DOM还未创建
console.log('created', this.message);
// 可以在这里发送请求、初始化数据等
},
beforeMount() {
// DOM还未挂载
console.log('beforeMount');
},
mounted() {
// DOM已挂载,可以访问DOM元素
console.log('mounted');
// 可以在这里操作DOM、初始化第三方库等
},
template: `
<div>
<p>{{ message }}</p>
</div>
`
};
const app = createApp({});
app.component('component-a', ComponentA);
app.mount('#app');
</script>
// 数据已更新,但DOM还未更新
console.log('beforeUpdate', this.count);
},
updated() {
// DOM已更新
console.log('updated', this.count);
// 可以在这里操作更新后的DOM
},
methods: {
increment() {
this.count++;
}
},
template: `
<div>
<p>计数:{{ count }}</p>
<button @click="increment">增加</button>
</div>
`
};
const app = createApp({});
app.component('counter-component', CounterComponent);
app.mount('#app');
</script>
data() {
return {
timer: null
};
},
mounted() {
// 创建一个定时器
this.timer = setInterval(() => {
console.log('定时器运行中');
}, 1000);
},
beforeUnmount() {
// 组件卸载之前,清理资源
console.log('beforeUnmount');
if (this.timer) {
clearInterval(this.timer);
}
},
unmounted() {
// 组件已卸载
console.log('unmounted');
},
template: `
<div>
<p>这是组件B</p>
</div>
`
};
const app = createApp({
setup() {
const show = ref(true);
return {
show
};
},
components: {
'component-b': ComponentB
}
});
app.mount('#app');
</script>
{
setup() {
const count = ref(0);
onBeforeMount(() => {
console.log('onBeforeMount');
});
onMounted(() => {
console.log('onMounted');
// 可以在这里操作DOM、初始化第三方库等
});
onBeforeUpdate(() => {
console.log('onBeforeUpdate', count.value);
});
onUpdated(() => {
console.log('onUpdated', count.value);
});
onBeforeUnmount(() => {
console.log('onBeforeUnmount');
// 清理资源
});
onUnmounted(() => {
console.log('onUnmounted');
});
const increment = () => {
count.value++;
};
return {
count,
increment
};
},
template: `
<div>
<p>计数:{{ count }}</p>
<button @click="increment">增加</button>
</div>
`
};
const app = createApp({});
app.component('component-c', ComponentC);
app.mount('#app');
</script>
},
async created() {
// 在created中发送请求,可以更早获取数据
this.loading = true;
try {
// 模拟API请求
await new Promise(resolve => setTimeout(resolve, 1000));
this.users = [
{ id: 1, name: '张三', age: 25 },
{ id: 2, name: '李四', age: 30 }
];
} finally {
this.loading = false;
}
},
template: `
<div>
<div v-if="loading">加载中...</div>
<ul v-else>
<li v-for="user in users" :key="user.id">
{{ user.name }},{{ user.age }}岁
</li>
</ul>
</div>
`
};
const app = createApp({});
app.component('user-list', UserList);
app.mount('#app');
</script>
);
if (canvas) {
const ctx = canvas.getContext('2d');
// 绘制图表
ctx.fillStyle = '#42b983';
ctx.fillRect(10, 10, 100, 50);
}
},
template: `
<div>
<canvas width="200" height="100"></canvas>
</div>
`
};
const app = createApp({});
app.component('chart-component', ChartComponent);
app.mount('#app');
</script>
data() {
return {
count: 0,
timer: null
};
},
mounted() {
// 创建定时器
this.timer = setInterval(() => {
this.count++;
}, 1000);
},
beforeUnmount() {
// 清理定时器
if (this.timer) {
clearInterval(this.timer);
this.timer = null;
}
},
template: `
<div>
<p>计时:{{ count }}秒</p>
</div>
`
};
const app = createApp({
setup() {
const show = ref(true);
return {
show
};
},
components: {
'timer-component': TimerComponent
}
});
app.mount('#app');
</script>
<style>
body {
font-family: Arial, sans-serif;
max-width: 800px;
margin: 50px auto;
padding: 20px;
}
button {
padding: 8px 16px;
margin-right: 10px;
margin-bottom: 10px;
background-color: #42b983;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
}
.logs {
margin-top: 20px;
padding: 15px;
background-color: #f5f5f5;
border-radius: 4px;
}
ul {
list-style: none;
padding: 0;
}
li {
padding: 5px 0;
border-bottom: 1px solid #ddd;
}
</style>
</head>
<body>
<div id="app">
<button @click="show = !show">切换显示</button>
<lifecycle-example v-if="show" :message="message"></lifecycle-example>
<button @click="message = '新消息'">改变消息</button>
</div>
<script>
const { createApp, ref } = Vue;
const LifecycleExample = {
props: ['message'],
data() {
return {
logs: []
};
},
beforeCreate() {
this.logs = [];
this.addLog('beforeCreate: 组件实例刚被创建');
},
created() {
this.addLog('created: 组件实例创建完成');
},
beforeMount() {
this.addLog('beforeMount: 组件挂载之前');
},
mounted() {
this.addLog('mounted: 组件已挂载');
},
beforeUpdate() {
this.addLog('beforeUpdate: 组件更新之前');
},
updated() {
this.addLog('updated: 组件更新完成');
},
beforeUnmount() {
this.addLog('beforeUnmount: 组件卸载之前');
},
unmounted() {
this.addLog('unmounted: 组件已卸载');
},
methods: {
addLog(message) {
const time = new Date().toLocaleTimeString();
this.logs.push(`[${time}] ${message}`);
}
},
template: `
<div>
<h3>生命周期示例</h3>
<p>消息:{{ message }}</p>
<div class="logs">
<h4>生命周期日志:</h4>
<ul>
<li v-for="(log, index) in logs" :key="index">{{ log }}</li>
</ul>
</div>
</div>
`
};
const app = createApp({
setup() {
const show = ref(true);
const message = ref('初始消息');
return {
show,
message
};
},
components: {
'lifecycle-example': LifecycleExample
}
});
app.mount('#app');
</script>
</body>
</html>