第一部分:Vue 3 基础入门
第12集:全局组件 vs 局部组件
在Vue 3中,组件可以分为全局组件和局部组件两种。它们的主要区别在于注册方式和使用范围。在本集中,我们将学习全局组件和局部组件的区别、使用方法和适用场景。
12.1 全局组件
全局组件是在应用级别注册的组件,注册后可以在应用的任何组件中使用,无需再次导入和注册。
12.1.1 全局组件的注册方法
在Vue 3中,我们可以使用app.component()方法来注册全局组件。
语法:
app.component('ComponentName', ComponentOptions)示例:
- 创建组件文件:
<!-- src/components/GlobalButton.vue -->
<template>
<button :class="['global-button', variant]">
<slot></slot>
</button>
</template>
<script setup>
import { defineProps } from 'vue'
const props = defineProps({
variant: {
type: String,
default: 'default'
}
})
</script>
<style scoped>
.global-button {
padding: 8px 16px;
border: none;
border-radius: 4px;
cursor: pointer;
font-size: 14px;
transition: background-color 0.3s;
}
.global-button.default {
background-color: #42b883;
color: white;
}
.global-button.primary {
background-color: #35495e;
color: white;
}
</style>- 在main.js中注册全局组件:
// src/main.js
import { createApp } from 'vue'
import App from './App.vue'
import GlobalButton from './components/GlobalButton.vue' // 导入组件
const app = createApp(App)
// 注册全局组件
app.component('GlobalButton', GlobalButton)
app.mount('#app')- 在任何组件中使用全局组件:
<template>
<div>
<h1>全局组件示例</h1>
<!-- 直接使用全局组件,无需导入 -->
<GlobalButton variant="default">默认按钮</GlobalButton>
<GlobalButton variant="primary">主要按钮</GlobalButton>
</div>
</template>
<script setup>
// 无需导入GlobalButton组件
</script>12.1.2 全局组件的特点
- 注册一次,全局可用:在应用级别注册后,任何组件都可以直接使用
- 无需重复导入:使用时无需再次导入组件
- 适合通用组件:适合在整个应用中频繁使用的通用组件
- 会增加打包体积:即使未使用,全局组件也会被打包到最终的bundle中
- 注册顺序重要:依赖其他组件的全局组件需要先注册依赖组件
12.1.3 全局组件的适用场景
- 通用UI组件,如按钮、输入框、卡片等
- 应用级别的布局组件,如导航栏、页脚等
- 第三方组件库,如Element Plus、Ant Design Vue等
- 在多个组件中频繁使用的组件
12.2 局部组件
局部组件是在组件内部注册的组件,只有注册它的组件及其子组件可以使用。
12.2.1 局部组件的注册方法
在Vue 3中,使用<script setup>语法时,局部组件的注册非常简单,只需导入组件即可使用。
示例:
- 创建组件文件:
<!-- src/components/LocalCard.vue -->
<template>
<div class="local-card">
<div v-if="title" class="card-title">
{{ title }}
</div>
<div class="card-content">
<slot></slot>
</div>
</div>
</template>
<script setup>
import { defineProps } from 'vue'
const props = defineProps({
title: {
type: String,
default: ''
}
})
</script>
<style scoped>
.local-card {
border: 1px solid #e0e0e0;
border-radius: 8px;
padding: 16px;
margin-bottom: 16px;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
}
.card-title {
font-size: 18px;
font-weight: bold;
margin-bottom: 12px;
color: #333;
}
.card-content {
color: #666;
}
</style>- 在父组件中导入和使用局部组件:
<template>
<div>
<h1>局部组件示例</h1>
<!-- 使用局部组件 -->
<LocalCard title="局部卡片1">
<p>这是局部卡片的内容</p>
</LocalCard>
<LocalCard title="局部卡片2">
<p>局部组件只能在注册它的组件中使用</p>
</LocalCard>
</div>
</template>
<script setup>
// 导入局部组件
import LocalCard from './components/LocalCard.vue'
</script>12.2.2 使用传统选项式API注册局部组件
如果使用传统的选项式API,我们需要在components选项中注册局部组件。
示例:
<template>
<div>
<h1>使用选项式API注册局部组件</h1>
<LocalComponent />
</div>
</template>
<script>
import LocalComponent from './components/LocalComponent.vue'
export default {
name: 'ParentComponent',
components: {
// 注册局部组件
LocalComponent
},
// 其他选项
}
</script>12.2.3 局部组件的特点
- 按需注册,按需使用:只在需要使用的组件中注册
- 需要导入后使用:使用前需要先导入组件
- 不会增加额外的打包体积:未使用的局部组件不会被打包
- 作用域受限:只能在注册它的组件及其子组件中使用
- 注册顺序不重要:局部组件的注册顺序不影响使用
12.2.4 局部组件的适用场景
- 仅在特定页面或组件中使用的组件
- 具有特定业务逻辑的组件
- 大型应用中,为了减少初始加载时间
- 组件之间存在复杂依赖关系的情况
- 需要动态加载的组件
12.3 全局组件 vs 局部组件对比
| 特性 | 全局组件 | 局部组件 |
|---|---|---|
| 注册方式 | app.component() |
导入后直接使用或在components选项中注册 |
| 使用范围 | 整个应用 | 注册它的组件及其子组件 |
| 导入需求 | 注册时需要导入,使用时无需导入 | 使用前需要导入 |
| 打包体积影响 | 会增加打包体积,即使未使用 | 未使用的组件不会被打包 |
| 注册顺序 | 重要,依赖组件需要先注册 | 不重要 |
| 适用场景 | 通用组件、频繁使用的组件 | 特定页面或组件中使用的组件 |
| 性能影响 | 初始加载时间可能增加 | 初始加载时间较少 |
| 代码组织 | 集中注册,便于管理 | 按需注册,更灵活 |
12.4 组件注册的最佳实践
优先使用局部组件:
- 局部组件按需加载,有助于减少初始打包体积
- 局部组件的作用域清晰,便于代码维护
- 局部组件的依赖关系明确,便于理解组件结构
合理使用全局组件:
- 对于通用UI组件,可以考虑注册为全局组件
- 避免将所有组件都注册为全局组件
- 第三方组件库通常会注册为全局组件
组件命名规范:
- 组件名使用PascalCase,如
GlobalButton、LocalCard - 全局组件可以使用统一的前缀,如
AppButton、AppCard - 文件名与组件名保持一致
- 组件名使用PascalCase,如
组件目录结构:
- 按功能模块组织组件
- 通用组件放在
components/common或components/ui目录 - 业务组件放在对应的功能模块目录
动态组件加载:
- 对于大型组件,可以考虑使用动态导入
- 使用
defineAsyncComponent实现组件懒加载 - 结合Vue Router实现路由级别的组件懒加载
12.5 综合示例:结合使用全局组件和局部组件
让我们创建一个综合示例,展示如何结合使用全局组件和局部组件。
示例:
- 注册全局组件:
// src/main.js
import { createApp } from 'vue'
import App from './App.vue'
import GlobalButton from './components/GlobalButton.vue'
import GlobalCard from './components/GlobalCard.vue'
const app = createApp(App)
// 注册全局组件
app.component('GlobalButton', GlobalButton)
app.component('GlobalCard', GlobalCard)
app.mount('#app')- 创建局部组件:
<!-- src/components/ProductItem.vue -->
<template>
<div class="product-item">
<img :src="product.image" :alt="product.name" class="product-image">
<h3 class="product-name">{{ product.name }}</h3>
<p class="product-price">{{ product.price }}元</p>
<GlobalButton @click="addtoCart">加入购物车</GlobalButton>
</div>
</template>
<script setup>
import { defineProps, defineEmits } from 'vue'
const props = defineProps({
product: {
type: Object,
required: true
}
})
const emit = defineEmits(['add-to-cart'])
function addtoCart() {
emit('add-to-cart', props.product)
}
</script>
<style scoped>
.product-item {
border: 1px solid #e0e0e0;
border-radius: 8px;
padding: 16px;
margin-bottom: 16px;
text-align: center;
}
.product-image {
width: 100%;
height: 200px;
object-fit: cover;
border-radius: 4px;
margin-bottom: 12px;
}
.product-name {
font-size: 18px;
margin-bottom: 8px;
}
.product-price {
font-size: 16px;
color: #e74c3c;
margin-bottom: 12px;
}
</style>- 在父组件中使用全局组件和局部组件:
<template>
<div class="app">
<h1>全局组件与局部组件结合使用</h1>
<!-- 使用全局组件GlobalCard -->
<GlobalCard title="产品列表">
<div class="product-list">
<!-- 使用局部组件ProductItem -->
<ProductItem
v-for="product in products"
:key="product.id"
:product="product"
@add-to-cart="addToCart"
/>
</div>
</GlobalCard>
<!-- 使用全局组件GlobalCard和GlobalButton -->
<GlobalCard title="购物车">
<div v-if="cart.length === 0" class="empty-cart">
<p>购物车为空</p>
</div>
<div v-else class="cart-items">
<div v-for="item in cart" :key="item.id" class="cart-item">
<span>{{ item.name }}</span>
<span>{{ item.price }}元</span>
<GlobalButton size="small" @click="removeFromCart(item.id)">删除</GlobalButton>
</div>
<div class="cart-total">
<strong>总计:{{ totalPrice }}元</strong>
</div>
</div>
</GlobalCard>
</div>
</template>
<script setup>
import { ref, computed } from 'vue'
// 导入局部组件
import ProductItem from './components/ProductItem.vue'
// 产品数据
const products = ref([
{
id: 1,
name: '苹果',
price: 5.5,
image: 'https://via.placeholder.com/300x200?text=Apple'
},
{
id: 2,
name: '香蕉',
price: 3.0,
image: 'https://via.placeholder.com/300x200?text=Banana'
},
{
id: 3,
name: '橙子',
price: 4.0,
image: 'https://via.placeholder.com/300x200?text=Orange'
}
])
// 购物车数据
const cart = ref([])
// 计算总价格
const totalPrice = computed(() => {
return cart.value.reduce((sum, item) => sum + item.price, 0).toFixed(2)
})
// 添加到购物车
function addToCart(product) {
cart.value.push({ ...product })
}
// 从购物车移除
function removeFromCart(id) {
cart.value = cart.value.filter(item => item.id !== id)
}
</script>
<style scoped>
.app {
max-width: 1200px;
margin: 0 auto;
padding: 20px;
}
.product-list {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
gap: 20px;
}
.cart-items {
display: flex;
flex-direction: column;
gap: 12px;
}
.cart-item {
display: flex;
justify-content: space-between;
align-items: center;
padding: 10px;
border-bottom: 1px solid #e0e0e0;
}
.cart-total {
margin-top: 16px;
text-align: right;
font-size: 18px;
}
.empty-cart {
text-align: center;
padding: 20px;
color: #666;
}
</style>12.6 动态组件注册
在某些情况下,我们可能需要动态注册组件。Vue 3提供了app.component()方法来动态注册全局组件,而局部组件可以通过动态导入来实现。
12.6.1 动态注册全局组件
示例:
// src/main.js
import { createApp } from 'vue'
import App from './App.vue'
const app = createApp(App)
// 动态导入并注册全局组件
const registerGlobalComponents = async () => {
try {
// 动态导入组件
const GlobalButton = await import('./components/GlobalButton.vue')
const GlobalCard = await import('./components/GlobalCard.vue')
// 注册全局组件
app.component('GlobalButton', GlobalButton.default)
app.component('GlobalCard', GlobalCard.default)
} catch (error) {
console.error('Failed to register global components:', error)
}
}
// 注册全局组件
registerGlobalComponents().then(() => {
// 组件注册完成后挂载应用
app.mount('#app')
})12.6.2 动态导入局部组件
示例:
<template>
<div>
<h1>动态导入局部组件</h1>
<button @click="toggleComponent">切换组件</button>
<component :is="currentComponent" />
</div>
</template>
<script setup>
import { ref, defineAsyncComponent } from 'vue'
// 定义当前组件
const currentComponent = ref(null)
// 动态导入组件
const loadComponent1 = defineAsyncComponent(() =>
import('./components/Component1.vue')
)
const loadComponent2 = defineAsyncComponent(() =>
import('./components/Component2.vue')
)
// 切换组件
function toggleComponent() {
currentComponent.value = currentComponent.value === loadComponent1 ? loadComponent2 : loadComponent1
}
// 初始加载第一个组件
currentComponent.value = loadComponent1
</script>本集小结
在本集中,我们学习了Vue 3中全局组件和局部组件的区别、使用方法和适用场景:
全局组件:
- 使用
app.component()方法注册 - 注册后在整个应用中可用
- 适合通用组件和频繁使用的组件
- 会增加打包体积
- 使用
局部组件:
- 在组件内部导入后直接使用(
<script setup>语法) - 只在注册它的组件及其子组件中可用
- 不会增加额外的打包体积
- 适合特定场景使用的组件
- 在组件内部导入后直接使用(
全局组件 vs 局部组件对比:
- 从注册方式、使用范围、打包体积等方面进行了对比
- 提供了选择建议
最佳实践:
- 优先使用局部组件
- 合理使用全局组件
- 遵循组件命名规范
- 合理组织组件目录结构
- 考虑动态组件加载
综合示例:
- 展示了如何结合使用全局组件和局部组件
- 实现了一个简单的产品列表和购物车功能
动态组件注册:
- 学习了如何动态注册全局组件
- 学习了如何动态导入局部组件
了解全局组件和局部组件的区别和适用场景,对于构建大型Vue应用至关重要。在实际开发中,我们应该根据具体需求选择合适的组件注册方式,以提高应用的性能和可维护性。
在下一集中,我们将学习组件间的数据传递,包括props的使用方法和最佳实践。