什么是组件
组件就是每一个 .vue 文件,无论是哪个文件夹下的。
pages/shop/category.vue ← 这是组件
pages/shop/goods.vue ← 这是组件
pages/shop/search.vue ← 这是组件
pages/index/index.vue ← 这是组件
pages/my/my.vue ← 这是组件
components/shop-sku.vue ← 这是组件
components/mod-nav-bar.vue ← 这是组件
组件的本质是”可复用的代码块”
<!-- 这是一个组件:按钮组件 -->
<!-- components/MyButton.vue -->
<template>
<button class="my-btn" @click="handleClick">
<slot></slot> <!-- 这里可以放任何内容 -->
</button>
</template>
<!-- 可以在任何地方复用这个组件 -->
<!-- pages/index.vue -->
<template>
<view>
<MyButton>登录</MyButton>
<MyButton>注册</MyButton>
<MyButton>提交订单</MyButton>
</view>
</template>
<!-- pages/shop.vue -->
<template>
<view>
<MyButton>加入购物车</MyButton>
<MyButton>立即购买</MyButton>
</view>
</template>
在项目中,只要满足以下条件的都是组件:
- 文件扩展名是
.vue - 可以被导入并在模板中使用
- 有自己独立的模板、脚本、样式
组件创建
组件创建 = .vue文件加载 + 实例化
组件创建的完整过程:
// 当这个代码执行时:
<template>
<Category /> <!-- 使用Category组件 -->
</template>
// 背后发生的过程:
组件的”加载”过程分解
// 步骤1:文件被加载(解析)
// 把 category.vue 这个文件读入内存
// 拆分成 template、script、style 三部分
// 步骤2:实例化(创建实例)
// 在内存中创建一个组件对象
const categoryInstance = {
// 有自己独立的数据
currentClassId: ref(""),
mainScrollTop: ref(0),
// 有自己的方法
onClassTab() {},
showSkuPop() {},
// 有自己的模板
template: `<view>...</view>`
}
// 步骤3:执行代码
// 运行 <script setup> 中的代码
// 发送请求、初始化数据
console.log('组件被创建了!')
const { data } = useGoodsCategoryApi() // 发送请求
可以观察到的现象
<!-- category.vue -->
<script setup>
// 这一行会在组件创建时立即执行
console.log('🔥 Category组件开始创建', new Date().toLocaleTimeString())
// 请求立即发送
const { data: categoryList } = useGoodsCategoryApi()
console.log('📦 请求已发送')
// 你可以看到请求的时间
// 控制台输出:
// 🔥 Category组件开始创建 14:30:25
// 📦 请求已发送 14:30:25
// (网络请求) 14:30:26 请求返回
</script>
Vue文件的执行顺序
完整的执行顺序
<!-- 1. 首先:<script setup> 部分(立即执行) -->
<script setup>
console.log('1. script setup 开始执行')
import { ref, onMounted } from 'vue'
import { useCartStore } from '@/stores/cart'
// 这些代码立即执行
console.log('2. 导入语句执行')
const count = ref(0)
console.log('3. 响应式数据创建')
// 生命周期钩子只是注册,不会立即执行
onMounted(() => {
console.log('6. onMounted 执行')
})
console.log('4. script setup 执行完毕')
</script>
<!-- 2. 然后:<template> 被编译成渲染函数 -->
<!-- template本身不"执行",它被编译成JavaScript渲染函数 -->
<!-- 3. 最后:<style> 被提取处理 -->
<!-- style是静态样式,不参与执行顺序 -->
<template>
<view>
<!-- 当组件渲染时,这里的内容被转换成DOM -->
{{ console.log('5. 模板渲染过程中执行') }}
</view>
</template>
<style lang="scss" scoped>
/* 样式被提取到单独的CSS,不参与JS执行顺序 */
</style>
实际执行顺序演示
<script setup>
// 第1组:立即执行
console.log('📦 [1] script setup 开始')
// 导入语句会提升,但执行顺序按书写顺序
import { ref, onMounted } from 'vue'
console.log('📦 [2] 导入完成')
// 响应式数据创建
const message = ref('hello')
console.log('📦 [3] 响应式数据创建')
// 普通函数定义
const handleClick = () => {
console.log('点击事件')
}
console.log('📦 [4] 函数定义完成')
// 生命周期钩子(只注册,不执行)
onMounted(() => {
console.log('📦 [7] onMounted 回调执行')
})
// 立即执行的逻辑
const initData = () => {
console.log('📦 [5] initData 函数执行')
}
initData()
console.log('📦 [6] script setup 结束')
</script>
<template>
<view>
<!-- 模板中的表达式在渲染时执行 -->
<text>{{ console.log('📦 [模板] 渲染过程中') }}</text>
<button @click="handleClick">按钮</button>
</view>
</template>
<!-- 控制台输出顺序: -->
📦 [1] script setup 开始
📦 [2] 导入完成
📦 [3] 响应式数据创建
📦 [4] 函数定义完成
📦 [5] initData 函数执行
📦 [6] script setup 结束
📦 [模板] 渲染过程中
📦 [7] onMounted 回调执行
为什么是这个顺序?
// Vue组件编译后的伪代码
export default {
setup() {
// 1. <script setup> 的内容被提取到这里
console.log('script setup 执行')
// 注册生命周期
onMounted(() => {
// 3. 最后执行
})
return {
// 返回给模板使用的数据和方法
}
},
// 2. <template> 被编译成 render 函数
render() {
console.log('模板渲染')
return h('view', ...)
}
}
特殊情况:script setup 内的异步
<script setup>
console.log('1. 同步代码立即执行')
// 异步代码会晚于同步代码
setTimeout(() => {
console.log('4. 异步回调执行')
}, 0)
// Promise 微任务
Promise.resolve().then(() => {
console.log('3. 微任务执行')
})
console.log('2. 同步代码继续执行')
</script>
<template>
<view>模板渲染</view>
</template>
// 输出顺序:
// 1. 同步代码立即执行
// 2. 同步代码继续执行
// 模板渲染
// 3. 微任务执行
// 4. 异步回调执行