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组件。

相关推荐
周万宁.FoBJ1 小时前
vue源码讲解之 reactive解析(仅proxy部分)
开发语言·javascript·ecmascript
乔磊1 小时前
我开发了一个 Ralph CLI
javascript
霪霖笙箫1 小时前
真授之以渔:我是怎么从"想给文章配几张图",一步步做出一个可发布 skill 的
前端·人工智能·开源
yzin1 小时前
【源码】【react】useCallback、useMemo、memo 原理
前端·react.js
CHU7290351 小时前
扭蛋机盲盒小程序前端功能设计及核心玩法介绍
前端·小程序
进击的尘埃1 小时前
Module Federation 2.0 共享策略翻车实录:版本协商、热更新与依赖冲突的排查工具链
javascript
毛骗导演2 小时前
OpenClaw Gateway RPC 运行时:一个 WebSocket 协议引擎的深度解剖
前端·架构
码路飞2 小时前
不会 Rust 也能玩 WebAssembly:3 个 npm install 就能用的 WASM 神器
前端·javascript·webassembly