深入理解 Vue 生命周期:created 与 mounted 的核心差异与实战指南
created 和 mounted 是 Vue 中最常用、也最容易混淆的两个生命周期钩子。很多人会问:“我到底应该把接口请求、DOM 操作、初始化逻辑放在哪里?”
下面从原理、执行时机、可用性、实战场景四个维度给你彻底讲透。
1. 核心对比表(强烈建议背下来)
| 维度 | created | mounted |
|---|---|---|
| 执行时机 | 实例创建完成后,DOM 未生成 | 实例挂载到 DOM 后,DOM 已真实渲染 |
| 能否访问 DOM | 不能($el 为 undefined,$refs 为空) | 可以($el、$refs、document.querySelector 均可用) |
| 数据响应式 | 已完成(data、props、computed、watch 均可用) | 已完成 |
| 子组件状态 | 子组件还未创建 | 子组件已完成 mounted |
| 典型用途 | 数据初始化、接口请求、事件总线监听 | DOM 操作、第三方库初始化、获取元素尺寸 |
| 是否可多次触发 | 首次创建时触发一次 | 首次创建时触发一次(keep-alive 组件切换时不触发) |
| Vue 3 Composition API 对应 | setup()(最早期) | onMounted() |
一句话总结:
- created = “数据准备阶段”(逻辑层面)
- mounted = “DOM 就绪阶段”(视觉层面)
2. 执行顺序全景图(父子组件 + keep-alive)
父组件 created
↓
子组件 created
↓
子组件 mounted
↓
父组件 mounted
- keep-alive 组件:首次走
created → mounted,之后切换只会触发activated / deactivated,不会再走 created/mounted。 - 异步组件(Suspense):子组件可能在父 mounted 之后才 mounted。
3. 代码示例对比
Options API 写法
<script>
export default {
data() {
return {
list: []
}
},
async created() {
// 推荐在这里发请求:数据准备好后立即渲染
const res = await fetch('/api/list')
this.list = res.data
},
mounted() {
// 这里才能安全操作 DOM
this.$refs.chart && this.initEcharts()
// 或
document.getElementById('box').style.height = '500px'
}
}
</script>
Vue 3 Composition API 写法(推荐)
<script setup>
import { ref, onMounted } from 'vue'
const list = ref([])
const chartRef = ref(null)
// created 对应:setup() 本身就是最早阶段
const fetchData = async () => {
const res = await fetch('/api/list')
list.value = res.data
}
fetchData() // 直接在 setup 中执行
// mounted 对应:onMounted
onMounted(() => {
if (chartRef.value) {
initEcharts(chartRef.value)
}
})
</script>
4. 实战指南:什么时候用哪个?
推荐在 created 中做的(优先级最高):
- 接口请求(最经典)
- 初始化组件内部状态
- 事件总线监听($bus.on)
- 根据 props 计算初始值
- 设置定时器(但注意在 beforeUnmount 清除)
必须在 mounted 中做的:
- 任何 DOM 操作(获取 offsetWidth、scrollHeight 等)
- 初始化第三方库:
- ECharts / Chart.js
- Swiper、BetterScroll
- 地图(高德、百度、Leaflet)
- 富文本编辑器(TinyMCE、WangEditor)
- 视频播放器(DPlayer、Video.js)
- 需要依赖真实 DOM 尺寸的动画、Canvas 初始化
this.$nextTick()仍然常用,但 mounted 之后已经不需要了
5. 常见错误与坑
错误1:在 created 中操作 DOM
created() {
this.$refs.box.style.color = 'red' // 无效!$refs 为空
}
错误2:把所有请求都放在 mounted
- 结果:白屏时间变长(数据请求晚了)
- 正确做法:created 发请求 + mounted 处理 DOM
错误3:在 mounted 中忘记清理副作用
mounted() {
this.timer = setInterval(...) // 必须在 beforeUnmount 中 clearInterval
}
错误4:依赖子组件 DOM
- 父组件 mounted 时,子组件已 mounted,但如果子组件是异步的(Suspense、动态组件),仍可能拿不到。
6. 最佳实践(2026 年现代 Vue 项目)
- 数据获取统一放在 created / setup()(更快渲染)
- DOM 操作统一放在 onMounted / mounted
- 复杂初始化逻辑抽成 composable
// useEcharts.js
export function useEcharts() {
const chartRef = ref(null)
onMounted(() => { ... })
return { chartRef }
}
- 结合 VueUse:
useMounted、onClickOutside等工具函数已帮你处理好时机 - SSR 项目:
created在服务端也会执行,mounted仅客户端执行(注意区分)
总结口诀
- created:数据就绪 → 适合发请求、初始化状态
- mounted:DOM 就绪 → 适合操作真实元素、初始化可视化库
- 核心差异:一个在“逻辑层”,一个在“渲染层”
掌握了这两个钩子的区别,你就真正理解了 Vue “数据驱动视图”的本质。
想继续深入哪个部分?
- Vue 3 setup() + 生命周期的完整对应关系
- keep-alive 下的生命周期行为
- 与 React useEffect 的对比
- 父子组件生命周期执行顺序的复杂场景
随时告诉我,我可以继续展开!