11Vue3 组件系统详解

一、组件基本概念

1.1 什么是组件

组件是Vue应用的可复用实例,包含:

  • 模板 (Template) - HTML结构

  • 数据 (Data) - 响应式状态

  • 方法 (Methods) - 交互逻辑

  • 生命周期钩子 (Lifecycle Hooks)

1.2 组件注册

全局注册

import { createApp } from 'vue'

const app = createApp({})

// 全局注册组件
app.component('MyComponent', {
  template: '<div>全局组件</div>'
})

局部注册(推荐)

import MyComponent from './MyComponent.vue'

export default {
  components: {
    MyComponent
  }
}

二、单文件组件 (SFC)

2.1 基本结构

<template>
  <!-- HTML模板 -->
  <div>{{ message }}</div>
</template>

<script setup>
// 逻辑部分
import { ref } from 'vue'

const message = ref('Hello Vue 3!')
</script>

<style scoped>
/* 样式部分 */
div {
  color: red;
}
</style>

2.2 <script setup> 语法糖(Composition API)

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

// 响应式数据
const count = ref(0)
const double = computed(() => count.value * 2)

// 方法
function increment() {
  count.value++
}

// 生命周期
onMounted(() => {
  console.log('组件已挂载')
})

// 自动暴露给模板
defineExpose({
  count,
  increment
})
</script>

三、Props 组件通信

3.1 定义Props

<!-- 子组件 -->
<script setup>
// 方式1:TypeScript类型
const props = defineProps<{
  title: string
  count?: number
  items: string[]
}>()

// 方式2:运行时声明
const props = defineProps({
  title: {
    type: String,
    required: true,
    default: '默认标题'
  },
  count: {
    type: Number,
    validator: (value) => value >= 0
  }
})
</script>

<template>
  <h2>{{ title }}</h2>
  <p>数量: {{ count }}</p>
</template>

3.2 使用Props

<!-- 父组件 -->
<template>
  <ChildComponent 
    :title="pageTitle" 
    :count="itemCount"
    :items="listItems"
  />
</template>

四、自定义事件

4.1 定义和触发事件

<!-- 子组件 -->
<script setup>
const emit = defineEmits<{
  // 带参数的事件
  (e: 'update:count', value: number): void
  // 不带参数的事件
  (e: 'submit'): void
}>()

function handleClick() {
  emit('update:count', 10)
  emit('submit')
}
</script>

4.2 使用v-model(双向绑定)

<!-- 自定义输入组件 -->
<script setup>
const modelValue = defineModel()

function updateValue(newValue) {
  modelValue.value = newValue
}
</script>

五、插槽 (Slots)

5.1 默认插槽

<!-- 子组件 -->
<template>
  <div class="card">
    <slot>默认内容</slot>
  </div>
</template>

<!-- 父组件使用 -->
<Card>
  <p>自定义内容</p>
</Card>

5.2 具名插槽

<!-- 子组件 -->
<template>
  <div class="layout">
    <header>
      <slot name="header"></slot>
    </header>
    <main>
      <slot></slot>
    </main>
    <footer>
      <slot name="footer"></slot>
    </footer>
  </div>
</template>

<!-- 父组件使用 -->
<Layout>
  <template #header>
    <h1>标题</h1>
  </template>
  
  <p>主要内容</p>
  
  <template #footer>
    <p>页脚</p>
  </template>
</Layout>

5.3 作用域插槽

<!-- 子组件 -->
<template>
  <ul>
    <li v-for="item in items" :key="item.id">
      <slot :item="item" :index="index"></slot>
    </li>
  </ul>
</template>

<!-- 父组件使用 -->
<ItemList :items="items">
  <template v-slot="{ item, index }">
    <span>{{ index + 1 }}. {{ item.name }}</span>
  </template>
</ItemList>

六、组件生命周期

<script setup>
import {
  onBeforeMount,    // 挂载前
  onMounted,        // 挂载后
  onBeforeUpdate,   // 更新前
  onUpdated,        // 更新后
  onBeforeUnmount,  // 卸载前
  onUnmounted,      // 卸载后
  onErrorCaptured   // 错误捕获
} from 'vue'

onMounted(() => {
  console.log('组件已挂载')
  // DOM操作、API请求
})

onUnmounted(() => {
  console.log('组件已卸载')
  // 清理定时器、取消订阅
})
</script>

七、依赖注入 (Provide/Inject)

7.1 提供数据

<!-- 祖先组件 -->
<script setup>
import { provide, ref } from 'vue'

const theme = ref('dark')
const user = ref({ name: '张三' })

// 提供响应式数据
provide('theme', theme)
provide('user', user)

// 提供方法
provide('updateTheme', (newTheme) => {
  theme.value = newTheme
})
</script>

7.2 注入数据

<!-- 后代组件 -->
<script setup>
import { inject } from 'vue'

// 注入数据
const theme = inject('theme')
const user = inject('user')
const updateTheme = inject('updateTheme')

// 带默认值
const config = inject('config', { color: 'blue' })

// 工厂函数
const api = inject('api', () => defaultApi())
</script>

八、动态组件

<template>
  <component 
    :is="currentComponent" 
    v-bind="componentProps"
  />
</template>

<script setup>
import { shallowRef } from 'vue'
import ComponentA from './ComponentA.vue'
import ComponentB from './ComponentB.vue'

const currentComponent = shallowRef(ComponentA)
const componentProps = {
  title: '动态组件'
}

// 切换组件
function switchComponent(component) {
  currentComponent.value = component
}
</script>

九、异步组件

<script setup>
import { defineAsyncComponent } from 'vue'

// 基础用法
const AsyncComponent = defineAsyncComponent(() =>
  import('./MyComponent.vue')
)

// 带配置选项
const AsyncComponentWithOptions = defineAsyncComponent({
  loader: () => import('./MyComponent.vue'),
  loadingComponent: LoadingComponent,
  errorComponent: ErrorComponent,
  delay: 200,
  timeout: 3000,
  suspensible: false,
  onError(error, retry, fail, attempts) {
    if (error.message.includes('fetch') && attempts <= 3) {
      retry()
    } else {
      fail()
    }
  }
})
</script>

十、组件最佳实践

10.1 命名规范

// 文件名: PascalCase
// MyComponent.vue

// 组件名: PascalCase(模板中使用)
app.component('MyComponent', { /* ... */ })

// Props: camelCase(定义),kebab-case(使用)
defineProps({
  userName: String  // 定义时用camelCase
})
// 使用时: <Component user-name="John" />

10.2 组件设计原则

  1. 单一职责:每个组件只做一件事

  2. 可复用性:通过props和slots提高复用性

  3. 组合优于继承:使用组合API构建复杂功能

  4. 响应式数据:合理使用ref和reactive

  5. 性能优化:使用v-memo、shallowRef等

10.3 组件通信选择

text
父 → 子:Props
子 → 父:自定义事件
兄弟组件:共同父级或Event Bus
深层嵌套:Provide/Inject
复杂状态:Pinia/Vuex

十一、示例:完整的TodoItem组件

<!-- TodoItem.vue -->
<template>
  <li 
    class="todo-item"
    :class="{ completed: todo.completed }"
  >
    <input
      type="checkbox"
      :checked="todo.completed"
      @change="toggleComplete"
    />
    
    <span class="todo-text">
      <slot :todo="todo">
        {{ todo.text }}
      </slot>
    </span>
    
    <button @click="$emit('delete', todo.id)">
      删除
    </button>
  </li>
</template>

<script setup>
import { computed } from 'vue'

const props = defineProps({
  todo: {
    type: Object,
    required: true
  }
})

const emit = defineEmits(['update:completed', 'delete'])

const todoStatus = computed({
  get: () => props.todo.completed,
  set: (value) => emit('update:completed', value)
})

function toggleComplete() {
  todoStatus.value = !todoStatus.value
}
</script>

<style scoped>
.todo-item {
  display: flex;
  align-items: center;
  padding: 8px;
  border-bottom: 1px solid #eee;
}

.todo-item.completed .todo-text {
  text-decoration: line-through;
  color: #999;
}
</style>

总结

Vue 3的组件系统提供了:

  1. 两种API风格:Options API(兼容Vue 2)和Composition API(推荐)

  2. 更好的TypeScript支持:完整的类型推断

  3. 更灵活的组合:Composition API的逻辑复用

  4. 更好的性能:更小的包体积、更快的渲染

  5. 更清晰的代码组织:<script setup>语法糖

掌握这些组件概念和用法,能够帮助你构建更健壮、可维护的Vue 3应用。

posted @ 2026-02-10 15:49  麻辣~香锅  阅读(4)  评论(0)    收藏  举报