Vue 生命周期完全指南

从创建到销毁,详解 Vue 组件的完整生命周期

Vue 的生命周期是指一个组件从创建、挂载、更新到销毁的整个过程。在这个过程中,Vue 会自动执行一些钩子函数,让我们可以在特定阶段编写自己的逻辑。理解生命周期是掌握 Vue 的基础。

一、Vue 2 vs Vue 3 生命周期对比

Vue 3 对生命周期做了一些调整,但核心概念不变。以下是两者的对比:
Vue3
beforeCreate
created
beforeMount
mounted
beforeUpdate
updated
beforeUnmount
unmounted
Vue2
beforeCreate
created
beforeMount
mounted
beforeUpdate
updated
beforeDestroy
destroyed

Vue 2 Vue 3 说明
beforeCreate beforeCreate 组件实例刚创建
created created 数据观测完成
beforeMount beforeMount 模板编译完成,还未挂载
mounted mounted 挂载到 DOM
beforeUpdate beforeUpdate 数据更新前
updated updated 数据更新完成
beforeDestroy beforeUnmount 组件销毁前
destroyed unmounted 组件已销毁

Vue 3 主要的变化是将 destroy 改为 unmount,语义更清晰。

二、各生命周期详解

2.1 beforeCreate

vue 复制代码
<script>
export default {
    beforeCreate() {
        console.log('beforeCreate - 实例创建前');
        // 此时 data、methods 还未初始化
        console.log(this.$data);  // undefined
    }
}
</script>

执行时机:组件实例刚创建,还没有任何响应式数据。

适用场景

  • 初始化非响应式的数据
  • 性能监控埋点

注意:这个阶段几乎不用,因为什么都还没准备好。


2.2 created

vue 复制代码
<script>
export default {
    data() {
        return { message: 'Hello' }
    },
    created() {
        console.log('created - 实例创建完成');
        // data 已经可用
        console.log(this.message);  // 'Hello'
        
        // 可以在此时发起接口请求
        this.fetchData();
    },
    methods: {
        async fetchData() {
            const res = await fetch('/api/data');
            this.data = await res.json();
        }
    }
}
</script>

执行时机:实例已完成数据观测(data)、方法(methods)的绑定。

适用场景

  • 发起初始接口请求
  • 初始化组件状态
  • 进行数据预处理

这是最常用的生命周期之一,常用于组件的初始化。


2.3 beforeMount

vue 复制代码
<script>
export default {
    beforeMount() {
        console.log('beforeMount - 挂载前');
        // 模板已经编译完成,但还未渲染到 DOM
        console.log(this.$el);  // undefined
        
        // 可以访问 this.$refs(但此时还是空对象)
        console.log(this.$refs.myDiv);  // undefined
    }
}
</script>

<template>
    <div ref="myDiv">Hello</div>
</template>

执行时机:模板已经编译完成,生成 render 函数,但还未创建 DOM 节点。

适用场景

  • 几乎不用
  • 某些需要提前获取 DOM 信息的场景(但不推荐)

2.4 mounted

vue 复制代码
<script>
export default {
    mounted() {
        console.log('mounted - 挂载完成');
        // DOM 已经渲染完成
        console.log(this.$refs.myDiv);  // <div>Hello</div>
        
        // 第三方库初始化
        this.initChart();
        this.initEventListeners();
    },
    methods: {
        initChart() {
            // ECharts 初始化需要 DOM
            this.chart = echarts.init(this.$refs.chart);
        }
    }
}
</script>

<template>
    <div ref="chart" style="width: 100%; height: 400px;"></div>
</template>

执行时机:组件已经挂载到 DOM,DOM 节点已创建并可访问。

适用场景

  • 第三方库初始化(ECharts、Swiper 等)
  • DOM 操作
  • 绑定事件监听器
  • 发起需要 DOM 的初始化请求

这是最常用的生命周期,用于需要操作 DOM 的场景。


2.5 beforeUpdate

vue 复制代码
<script>
export default {
    data() {
        return { count: 0 }
    },
    beforeUpdate() {
        console.log('beforeUpdate - 更新前');
        // 可以在更新前获取更新前的 DOM 状态
        console.log(this.$refs.counter.textContent);  // 旧值
        
        // 谨慎使用:这里修改数据会导致死循环
    },
    methods: {
        increment() {
            this.count++;
        }
    }
}
</script>

<template>
    <button ref="counter" @click="increment">{{ count }}</button>
</template>

执行时机:数据发生变化,但 DOM 还未更新。

适用场景

  • 获取更新前的 DOM 状态
  • 在 DOM 更新前移除事件监听
  • 配合 updated 做性能优化

2.6 updated

vue 复制代码
<script>
export default {
    data() {
        return { count: 0 }
    },
    updated() {
        console.log('updated - 更新完成');
        // DOM 已经更新完成
        console.log(this.$refs.counter.textContent);  // 新值
        
        // 重新计算基于 DOM 的数据
        this.recalculatePosition();
    },
    methods: {
        recalculatePosition() {
            // 基于新的 DOM 状态做处理
        }
    }
}
</script>

执行时机:DOM 已更新完成。

适用场景

  • 基于最新 DOM 做处理
  • 重新计算布局
  • 触发基于更新的动画

注意:避免在 updated 中修改 data,否则会触发死循环。


2.7 beforeUnmount(Vue 3)/ beforeDestroy(Vue 2)

vue 复制代码
<script>
export default {
    beforeUnmount() {
        console.log('beforeUnmount - 销毁前');
        
        // 清理定时器
        clearInterval(this.timer);
        clearTimeout(this.timeout);
        
        // 移除事件监听
        window.removeEventListener('resize', this.handleResize);
        
        // 取消未完成的请求
        this.controller?.abort();
    },
    mounted() {
        this.timer = setInterval(() => {
            this.fetchData();
        }, 5000);
        
        window.addEventListener('resize', this.handleResize);
    }
}
</script>

执行时机:组件即将被销毁,但 DOM 还存在。

适用场景

  • 清理定时器
  • 移除事件监听
  • 取消未完成的网络请求
  • 清理 Vuex 或 Pinia 订阅

2.8 unmounted(Vue 3)/ destroyed(Vue 2)

vue 复制代码
<script>
export default {
    unmounted() {
        console.log('unmounted - 销毁完成');
        
        // 理论上这里不应该再有 DOM 操作
        // 但某些情况下可能还能访问到
        
        // 可以做最终的清理工作
        this.cleanup();
    }
}
</script>

执行时机:组件已销毁,DOM 已移除。

适用场景

  • 几乎不用
  • 调试用

三、Composition API 中的生命周期

Vue 3 的 Composition API 使用 setup 函数,生命周期钩子需要通过 import 引入:

vue 复制代码
<script setup>
import { 
    onBeforeMount, 
    onMounted, 
    onBeforeUpdate, 
    onUpdated,
    onBeforeUnmount, 
    onUnmounted 
} from 'vue'

// 等同于 beforeCreate + created
const message = ref('Hello')

onBeforeMount(() => {
    console.log('onBeforeMount')
})

onMounted(() => {
    console.log('onMounted')
    
    // 第三方库初始化
    echarts.init(document.querySelector('.chart'))
})

onBeforeUpdate(() => {
    console.log('onBeforeUpdate')
})

onUpdated(() => {
    console.log('onUpdated')
})

onBeforeUnmount(() => {
    // 清理定时器
    clearInterval(timer)
})

onUnmounted(() => {
    console.log('onUnmounted')
})
</script>
选项 API Composition API
beforeCreate setup(直接写)
created setup(直接写)
beforeMount onBeforeMount
mounted onMounted
beforeUpdate onBeforeUpdate
updated onUpdated
beforeUnmount onBeforeUnmount
unmounted onUnmounted

四、生命周期实际应用

4.1 接口请求

vue 复制代码
<script>
export default {
    data() {
        return {
            list: [],
            loading: false
        }
    },
    
    // 推荐:created 中发起请求
    created() {
        this.fetchList();
    },
    
    // 或者:mounted 中发起请求(需要 DOM 时用)
    mounted() {
        this.initChart();
    },
    
    methods: {
        async fetchList() {
            this.loading = true;
            try {
                const res = await fetch('/api/list');
                this.list = await res.json();
            } finally {
                this.loading = false;
            }
        }
    }
}
</script>

4.2 第三方库初始化

vue 复制代码
<script>
import echarts from 'echarts'

export default {
    mounted() {
        // ECharts 需要 DOM
        this.chart = echarts.init(this.$refs.chart)
        this.chart.setOption({
            title: { text: 'ECharts' },
            series: [{ type: 'pie', data: [1, 2, 3] }]
        })
        
        // 监听窗口变化
        window.addEventListener('resize', this.handleResize)
    },
    
    beforeUnmount() {
        // 清理
        window.removeEventListener('resize', this.handleResize)
        this.chart?.dispose()
    },
    
    methods: {
        handleResize() {
            this.chart?.resize()
        }
    }
}
</script>

<template>
    <div ref="chart" style="width: 100%; height: 300px;"></div>
</template>

4.3 定时器管理

vue 复制代码
<script>
export default {
    data() {
        return {
            count: 0,
            timer: null
        }
    },
    
    mounted() {
        // 启动定时器
        this.timer = setInterval(() => {
            this.count++;
        }, 1000);
    },
    
    beforeUnmount() {
        // 清理定时器(必须!)
        if (this.timer) {
            clearInterval(this.timer);
            this.timer = null;
        }
    }
}
</script>

4.4 路由守卫配合

vue 复制代码
<script>
export default {
    // 每次进入组件都会执行
    created() {
        // 从缓存页面回来时,created 不会再次执行
        // 使用 watch 监听 $route 可以解决这个问题
        this.fetchData();
    },
    
    watch: {
        $route(to, from) {
            // 路由参数变化时重新获取数据
            if (to.params.id !== from.params.id) {
                this.fetchData();
            }
        }
    },
    
    methods: {
        fetchData() {
            console.log('fetch data...');
        }
    }
}
</script>

五、KeepAlive 缓存与生命周期

使用 <keep-alive> 缓存组件时,生命周期会有所变化:

vue 复制代码
<!-- 父组件 -->
<template>
    <keep-alive :include="['UserList']">
        <router-view />
    </keep-alive>
</template>

再次进入
离开缓存
首次进入
首次进入
onMounted
onActivated
onActivated(不触发 mounted)

vue 复制代码
<script>
export default {
    // 第一次进入
    mounted() {
        console.log('mounted - 首次挂载');
        this.fetchData();
    },
    
    // 被缓存的组件激活时
    activated() {
        console.log('activated - 组件激活');
        // 每次从缓存回来都会执行
        this.refreshData();
    },
    
    // 被缓存的组件停用时
    deactivated() {
        console.log('deactivated - 组件停用');
        // 组件被缓存时执行
    }
}
</script>
钩子 触发时机
mounted 首次渲染时执行
activated 每次从缓存激活时执行
deactivated 组件被缓存时执行

适用场景:

  • 列表页缓存,返回时保留滚动位置
  • 表单页缓存,返回时不丢失填写数据

六、父子组件生命周期执行顺序

6.1 创建过程

子组件 父组件 子组件 父组件 beforeCreate created beforeMount beforeCreate created beforeMount mounted mounted

javascript 复制代码
// 父组件
created() { console.log('父 created') }
mounted() { console.log('父 mounted') }

// 子组件
created() { console.log('子 created') }
mounted() { console.log('子 mounted') }

// 输出顺序:
// 父 created
// 子 created
// 子 mounted
// 父 mounted

6.2 更新过程

javascript 复制代码
// 父组件
beforeUpdate() { console.log('父 beforeUpdate') }
updated() { console.log('父 updated') }

// 子组件
beforeUpdate() { console.log('子 beforeUpdate') }
updated() { console.log('子 updated') }

// 当父组件数据变化触发更新:
// 父 beforeUpdate
// 子 beforeUpdate
// 子 updated
// 父 updated

6.3 销毁过程

javascript 复制代码
// 父组件
beforeUnmount() { console.log('父 beforeUnmount') }
unmounted() { console.log('父 unmounted') }

// 子组件
beforeUnmount() { console.log('子 beforeUnmount') }
unmounted() { console.log('子 unmounted') }

// 销毁父组件时:
// 父 beforeUnmount
// 子 beforeUnmount
// 子 unmounted
// 父 unmounted

七、常见问题

7.1 created 和 mounted 有什么区别?

阶段 created mounted
DOM 不可访问 可访问
数据 已响应式 已响应式
适用 接口请求 DOM 操作

简单理解:需要操作 DOM 用 mounted,只需要数据用 created。

7.2 为什么定时器要在 beforeUnmount 清理?

vue 复制代码
<!-- 不清理的后果 -->
<template>
    <button @click="show = !show">切换</button>
    <Child v-if="show" />
</template>

<script>
export default {
    components: { Child },
    data() { return { show: true } }
}
</script>
javascript 复制代码
// Child.vue
mounted() {
    this.timer = setInterval(() => {
        console.log('timer running');
    }, 1000);
}

// 切换 v-if 时,组件被销毁
// 如果不清理定时器,定时器还在运行
// 会导致内存泄漏和意外行为

7.3 为什么 updated 中修改数据会死循环?

javascript 复制代码
updated() {
    // 错误示例
    this.count++;  // 这会再次触发 updated
    // 死循环!
}

// 正确做法:使用 watch 监听变化
watch: {
    count(newVal) {
        // 处理变化
    }
}

7.4 异步组件的生命周期

vue 复制代码
<script>
import { defineAsyncComponent } from 'vue'

const AsyncComponent = defineAsyncComponent(() => 
    import('./HeavyComponent.vue')
)
</script>

<template>
    <AsyncComponent v-if="show" />
    <button @click="show = true">加载</button>
</template>

异步组件的生命周期:

  • 加载中:触发 loading 钩子
  • 加载成功:触发 resolved 钩子
  • 加载失败:触发 error 钩子
  • 实际组件:正常生命周期

八、总结

Vue 生命周期是开发中非常重要的概念,核心要点:

  1. created:最适合发起初始请求的时机
  2. mounted:需要操作 DOM 时的首选
  3. beforeUnmount:清理定时器、事件监听、取消请求
  4. keep-alive:使用 activated/deactivated 管理缓存组件

理解生命周期,能够帮助我们:

  • 在正确的时机做正确的事
  • 避免内存泄漏
  • 优化性能
  • 解决奇怪的问题
相关推荐
冴羽yayujs1 小时前
资深前端都在用的 9 个调试偏方
前端·javascript·调试
Amumu121381 小时前
CSS移动端
前端·css·css3
lichenyang4531 小时前
组件设计模式与通信
前端·javascript·设计模式
im_AMBER2 小时前
前端性能优化之首屏提速
前端·学习·性能优化
lxh01132 小时前
计算右侧小于当前元素的个数 题解
javascript·数据结构·算法
天天向上10242 小时前
vue 大屏适配的一种实现思路
前端·javascript·vue.js
SuperEugene2 小时前
Vue/Vite 多环境配置实战:dev、test、prod 差异区分与避坑指南|Vue 工程化篇
前端·javascript·vue.js
结网的兔子2 小时前
前端学习笔记(实战准备篇)——用vite构建一个项目【吐血整理】
前端·学习·elementui·npm·node.js·vue
kyriewen2 小时前
盒模型:CSS 世界的物理法则,margin 塌陷与 padding 的恩怨情仇
前端·css·html