第一部分:Vue 3 基础入门

第12集:全局组件 vs 局部组件

在Vue 3中,组件可以分为全局组件和局部组件两种。它们的主要区别在于注册方式和使用范围。在本集中,我们将学习全局组件和局部组件的区别、使用方法和适用场景。

12.1 全局组件

全局组件是在应用级别注册的组件,注册后可以在应用的任何组件中使用,无需再次导入和注册。

12.1.1 全局组件的注册方法

在Vue 3中,我们可以使用app.component()方法来注册全局组件。

语法

app.component('ComponentName', ComponentOptions)

示例

  1. 创建组件文件
<!-- 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>
  1. 在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')
  1. 在任何组件中使用全局组件
<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中,使用&lt;script setup&gt;语法时,局部组件的注册非常简单,只需导入组件即可使用。

示例

  1. 创建组件文件
<!-- 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>
  1. 在父组件中导入和使用局部组件
<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 组件注册的最佳实践

  1. 优先使用局部组件

    • 局部组件按需加载,有助于减少初始打包体积
    • 局部组件的作用域清晰,便于代码维护
    • 局部组件的依赖关系明确,便于理解组件结构
  2. 合理使用全局组件

    • 对于通用UI组件,可以考虑注册为全局组件
    • 避免将所有组件都注册为全局组件
    • 第三方组件库通常会注册为全局组件
  3. 组件命名规范

    • 组件名使用PascalCase,如GlobalButtonLocalCard
    • 全局组件可以使用统一的前缀,如AppButtonAppCard
    • 文件名与组件名保持一致
  4. 组件目录结构

    • 按功能模块组织组件
    • 通用组件放在components/commoncomponents/ui目录
    • 业务组件放在对应的功能模块目录
  5. 动态组件加载

    • 对于大型组件,可以考虑使用动态导入
    • 使用defineAsyncComponent实现组件懒加载
    • 结合Vue Router实现路由级别的组件懒加载

12.5 综合示例:结合使用全局组件和局部组件

让我们创建一个综合示例,展示如何结合使用全局组件和局部组件。

示例

  1. 注册全局组件
// 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')
  1. 创建局部组件
<!-- 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>
  1. 在父组件中使用全局组件和局部组件
<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()方法注册
    • 注册后在整个应用中可用
    • 适合通用组件和频繁使用的组件
    • 会增加打包体积
  • 局部组件

    • 在组件内部导入后直接使用(&lt;script setup&gt;语法)
    • 只在注册它的组件及其子组件中可用
    • 不会增加额外的打包体积
    • 适合特定场景使用的组件
  • 全局组件 vs 局部组件对比

    • 从注册方式、使用范围、打包体积等方面进行了对比
    • 提供了选择建议
  • 最佳实践

    • 优先使用局部组件
    • 合理使用全局组件
    • 遵循组件命名规范
    • 合理组织组件目录结构
    • 考虑动态组件加载
  • 综合示例

    • 展示了如何结合使用全局组件和局部组件
    • 实现了一个简单的产品列表和购物车功能
  • 动态组件注册

    • 学习了如何动态注册全局组件
    • 学习了如何动态导入局部组件

了解全局组件和局部组件的区别和适用场景,对于构建大型Vue应用至关重要。在实际开发中,我们应该根据具体需求选择合适的组件注册方式,以提高应用的性能和可维护性。

在下一集中,我们将学习组件间的数据传递,包括props的使用方法和最佳实践。

« 上一篇 组件化思想与组件定义 下一篇 » Props:组件间数据传递