Vue 3 单文件组件加载顺序详解

Vue 3 单文件组件加载顺序详解

在 Vue 3 的单文件组件(.vue 文件)中,<template><script><style> 三个部分的加载和执行顺序遵循明确的时间线。下面我通过示例详细说明:

📊 总体加载时间线

复制代码
编译阶段:<template>解析 → <script>编译 → <style>提取
运行时:<script>执行 → 组件实例化 → 虚拟DOM创建 → 挂载DOM → 应用<style>

一、基础示例与分析

示例组件

vue 复制代码
<!-- ExampleComponent.vue -->
<template>
  <div class="container">
    <h1>{{ title }}</h1>
    <button @click="increment">点击: {{ count }}</button>
  </div>
</template>

<script setup>
// 阶段1: <script>执行开始
console.log('1. <script> 开始执行')

import { ref, onBeforeMount, onMounted } from 'vue'

// 阶段2: 声明响应式数据
const title = ref('Vue 3 组件')
const count = ref(0)

// 阶段3: 定义方法
const increment = () => {
  count.value++
  console.log('点击触发,当前count:', count.value)
}

// 阶段4: 注册生命周期钩子
onBeforeMount(() => {
  console.log('4. onBeforeMount: 组件挂载前')
  console.log('   容器是否存在:', !!document.querySelector('.container'))
})

onMounted(() => {
  console.log('6. onMounted: 组件已挂载')
  console.log('   容器是否存在:', !!document.querySelector('.container'))
  console.log('   计算后颜色:', window.getComputedStyle(document.querySelector('.container')).color)
})

// 阶段5: 同步代码结束
console.log('2. <script> 同步代码执行完毕')

// 微任务检查
Promise.resolve().then(() => {
  console.log('5. 微任务执行')
})

// 宏任务检查
setTimeout(() => {
  console.log('7. 宏任务执行')
}, 0)
</script>

<style scoped>
.container {
  color: blue;
  padding: 20px;
  background-color: #f5f5f5;
}
h1 {
  font-size: 24px;
  transition: color 0.3s ease;
}
</style>

控制台输出顺序

复制代码
1. <script> 开始执行
2. <script> 同步代码执行完毕
3. 模板编译为渲染函数
4. onBeforeMount: 组件挂载前
   容器是否存在: false
5. 微任务执行
6. onMounted: 组件已挂载
   容器是否存在: true
   计算后颜色: rgb(0, 0, 238)  // blue颜色值
7. 宏任务执行

二、详细执行阶段解析

阶段1:编译时处理(构建阶段)

javascript 复制代码
// 在构建过程中(npm run build时):
1. 解析 .vue 文件
2. 提取 <template> 编译为渲染函数
3. 提取 <script> 转换为JavaScript
4. 提取 <style> 处理为CSS
5. 生成最终bundle文件

阶段2:运行时执行(浏览器中)

javascript 复制代码
// 实际执行顺序:

// Part 1: Script执行阶段
① 执行 <script setup> 中的同步代码
   - 导入依赖
   - 声明响应式数据
   - 定义方法
   - 注册生命周期钩子

// Part 2: 挂载准备阶段
② 触发 onBeforeMount 钩子
   - 此时DOM尚未创建
   - 无法访问模板中的元素

// Part 3: 模板渲染阶段
③ 调用渲染函数,创建虚拟DOM
④ 虚拟DOM对比和更新
⑤ 创建实际DOM元素
⑥ 将DOM插入页面

// Part 4: 样式应用阶段
⑦ 应用<style>中的样式
   - 如果是scoped样式,添加data-v-*属性
   - 如果是全局样式,直接应用
   - 计算最终样式

// Part 5: 挂载完成阶段
⑧ 触发 onMounted 钩子
   - DOM完全可用
   - 样式已计算完成
   - 可执行DOM相关操作

三、父子组件加载顺序

父子组件示例

vue 复制代码
<!-- ParentComponent.vue -->
<template>
  <div class="parent">
    <h2>父组件</h2>
    <ChildComponent />
  </div>
</template>

<script setup>
import { onBeforeMount, onMounted } from 'vue'
import ChildComponent from './ChildComponent.vue'

console.log('Parent: 1. script开始')

onBeforeMount(() => {
  console.log('Parent: 4. onBeforeMount')
})

onMounted(() => {
  console.log('Parent: 7. onMounted')
})

console.log('Parent: 2. script结束')
</script>
vue 复制代码
<!-- ChildComponent.vue -->
<template>
  <div class="child">子组件</div>
</template>

<script setup>
import { onBeforeMount, onMounted } from 'vue'

console.log('Child: 1. script开始')

onBeforeMount(() => {
  console.log('Child: 5. onBeforeMount')
})

onMounted(() => {
  console.log('Child: 6. onMounted')
})

console.log('Child: 2. script结束')
</script>

控制台输出

复制代码
Parent: 1. script开始
Parent: 2. script结束
Child: 1. script开始
Child: 2. script结束
3. 模板编译阶段
Parent: 4. onBeforeMount
Child: 5. onBeforeMount
Child: 6. onMounted
Parent: 7. onMounted

父子组件加载规则

复制代码
父组件<script> → 子组件<script> → 父组件onBeforeMount → 子组件onBeforeMount → 子组件onMounted → 父组件onMounted

四、样式加载的特殊性

样式应用时机验证

vue 复制代码
<template>
  <div ref="el" class="styled-box">样式测试</div>
</template>

<script setup>
import { ref, onBeforeMount, onMounted, nextTick } from 'vue'

const el = ref(null)

onBeforeMount(() => {
  console.log('onBeforeMount - 样式未应用')
})

onMounted(async () => {
  console.log('onMounted - 初始检查:')
  const style = window.getComputedStyle(el.value)
  console.log('  颜色:', style.color)
  console.log('  宽度:', style.width)
  
  // 等待一次更新循环
  await nextTick()
  console.log('nextTick后 - 最终检查:')
  console.log('  颜色:', window.getComputedStyle(el.value).color)
  console.log('  宽度:', window.getComputedStyle(el.value).width)
})
</script>

<style scoped>
.styled-box {
  color: red;
  width: 200px;
  padding: 20px;
  background: linear-gradient(45deg, #f3f3f3, #e0e0e0);
  transition: all 0.3s ease;
}
</style>

关键发现

  • onMounted 中,样式已应用 但可能未完全计算
  • 复杂样式(渐变、过渡)可能需要 nextTick() 后才能完全生效
  • Scoped样式在挂载时注入,全局样式可能更早加载

五、实际项目注意事项

1. DOM操作的正确时机

vue 复制代码
<script setup>
import { onMounted, nextTick } from 'vue'

// ❌ 错误:在onMounted中立即获取元素尺寸
onMounted(() => {
  const width = element.offsetWidth // 可能为0
})

// ✅ 正确:等待一次更新循环
onMounted(async () => {
  await nextTick()
  const width = element.offsetWidth // 正确的尺寸
})
</script>

2. 第三方库初始化的时机

vue 复制代码
<script setup>
import { onMounted } from 'vue'
import * as echarts from 'echarts'

const chartRef = ref(null)

onMounted(() => {
  // ✅ 正确:在onMounted中初始化,此时DOM已存在
  const chart = echarts.init(chartRef.value)
  
  // 如果依赖样式计算,可以添加nextTick
  nextTick(() => {
    chart.resize() // 确保图表尺寸正确
  })
})
</script>

3. 数据获取的优化

vue 复制代码
<script setup>
import { onMounted, ref } from 'vue'

const data = ref(null)
const isLoading = ref(false)

// 方案A:在onMounted中获取
onMounted(async () => {
  isLoading.value = true
  data.value = await fetchData()
  isLoading.value = false
})

// 方案B:立即开始,显示加载状态
const init = async () => {
  isLoading.value = true
  data.value = await fetchData()
  isLoading.value = false
}
init() // 在setup中调用
</script>

六、总结与最佳实践

加载顺序总结

复制代码
1. 编译阶段
   - template编译为渲染函数
   - script转换为JavaScript
   - style处理为CSS

2. 运行时阶段
   - 执行<script>同步代码
   - 触发onBeforeMount
   - 创建虚拟DOM → 真实DOM
   - 应用<style>样式
   - 触发onMounted
   - 微任务执行
   - 宏任务执行

3. 更新阶段
   - 数据变化触发重新渲染
   - 应用更新后的样式

最佳实践指南

  1. 数据初始化 :在 <script> 中定义,在 onMounted 中填充
  2. DOM操作 :总是在 onMounted 之后执行
  3. 样式依赖操作 :使用 nextTick() 确保样式计算完成
  4. 第三方库 :在 onMounted 中初始化
  5. 事件监听onMounted 中添加,onUnmounted 中移除

记忆口诀

复制代码
script先执行,template后渲染
样式最后加,挂载才完成
父子有顺序,先子后父行
操作要等待,nextTick最稳

理解这个加载顺序能帮助您避免常见的时序bug,编写更健壮的Vue 3组件。

相关推荐
于慨1 天前
Lambda 表达式、方法引用(Method Reference)语法
java·前端·servlet
石小石Orz1 天前
油猴脚本实现生产环境加载本地qiankun子应用
前端·架构
从前慢丶1 天前
前端交互规范(Web 端)
前端
像我这样帅的人丶你还1 天前
别再让JS耽误你进步了。
css·vue.js
@yanyu6661 天前
07-引入element布局及spring boot完善后端
javascript·vue.js·spring boot
CHU7290351 天前
便捷约玩,沉浸推理:线上剧本杀APP功能版块设计详解
前端·小程序
GISer_Jing1 天前
Page-agent MCP结构
前端·人工智能
王霸天1 天前
💥别再抄网上的Scale缩放代码了!50行源码教你写一个永不翻车的大屏适配
前端·vue.js·数据可视化
小领航1 天前
用 Three.js + Vue 3 打造炫酷的 3D 行政地图可视化组件
前端·github
@大迁世界1 天前
2026年React大洗牌:React Hooks 将迎来重大升级
前端·javascript·react.js·前端框架·ecmascript