03-组件系统

Vue 组件系统

1. 组件基础

注册组件

全局注册

// main.js
import { createApp } from 'vue'
import MyComponent from './components/MyComponent.vue'

const app = createApp({})
app.component('MyComponent', MyComponent)
app.mount('#app')

局部注册

<script setup>
import MyComponent from './components/MyComponent.vue'
// 引入后即可在模板中使用
</script>

<template>
<MyComponent />
</template>

2. Props

声明 Props

<!-- ChildComponent.vue -->
<script setup>
// 方式一:数组形式
defineProps(['title', 'content', 'visible'])

// 方式二:对象形式(推荐)
defineProps({
title: {
type: String,
required: true
},
content: {
type: String,
default: '默认内容'
},
visible: {
type: Boolean,
default: true
},
count: {
type: Number,
validator(value) {
return value >= 0
}
}
})
</script>

<template>
<div v-if="visible">
<h2>{{ title }}</h2>
<p>{{ content }}</p>
</div>
</template>

使用组件并传递 Props

<script setup>
import ChildComponent from './ChildComponent.vue'
</script>

<template>
<ChildComponent
title="文章标题"
content="这是文章内容"
:visible="true"
:count="10"
/>

<!-- 动态传递 -->
<ChildComponent
v-bind="{ title: '动态标题', content: '动态内容' }"
/>
</template>

Props 类型

defineProps({
  // 基础类型检查
  propA: Number,

// 多种可能的类型
propB: [String, Number],

// 必填字符串
propC: {
type: String,
required: true
},

// 带默认值的数字
propD: {
type: Number,
default: 100
},

// 对象默认值需要用工厂函数
propE: {
type: Object,
default() {
return { message: 'hello' }
}
},

// 自定义验证函数
propF: {
validator(value) {
return ['success', 'warning', 'danger'].includes(value)
}
}
})

3. 事件 (Emits)

声明和触发事件

<!-- ChildComponent.vue -->
<script setup>
const emit = defineEmits(['change', 'update', 'delete'])

function handleChange(value) {
emit('change', value)
}

function handleDelete(id) {
emit('delete', id)
}
</script>

<template>
<button @click="handleChange('new value')">修改</button>
<button @click="handleDelete(1)">删除</button>
</template>

<!-- 父组件 -->
<script setup>
import ChildComponent from './ChildComponent.vue'

function onValueChange(value) {
  console.log('值改变了:', value)
}

function onDelete(id) {
  console.log('删除:', id)
}
</script>

<template>
  <ChildComponent 
    @change="onValueChange" 
    @delete="onDelete" 
  />
</template>

带验证的 Emits

const emit = defineEmits({
  // 没有验证
  click: null,

// 验证提交的事件
submit: (payload) => {
if (payload.email && payload.password) {
return true
} else {
console.warn('Invalid submit event payload!')
return false
}
}
})

4. v-model

组件上的 v-model

<!-- CustomInput.vue -->
<script setup>
defineProps(['modelValue'])
defineEmits(['update:modelValue'])
</script>

<template>
<input
:value="modelValue"
@input="$emit('update:modelValue', $event.target.value)"
/>
</template>

<!-- 父组件 -->
<script setup>
import { ref } from 'vue'
import CustomInput from './CustomInput.vue'

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

<template>
  <CustomInput v-model="message" />
  <p>{{ message }}</p>
</template>

多个 v-model

<!-- UserName.vue -->
<script setup>
defineProps(['firstName', 'lastName'])
defineEmits(['update:firstName', 'update:lastName'])
</script>

<template>
<input
:value="firstName"
@input="$emit('update:firstName', $event.target.value)"
/>
<input
:value="lastName"
@input="$emit('update:lastName', $event.target.value)"
/>
</template>

<!-- 父组件 -->
<template>
  <UserName 
    v-model:first-name="first" 
    v-model:last-name="last" 
  />
</template>

自定义 v-model 修饰符

<!-- MyInput.vue -->
<script setup>
const props = defineProps({
  modelValue: String,
  modelModifiers: { default: () => ({}) }
})

const emit = defineEmits(['update:modelValue'])

function onInput(event) {
let value = event.target.value
if (props.modelModifiers.capitalize) {
value = value.charAt(0).toUpperCase() + value.slice(1)
}
emit('update:modelValue', value)
}
</script>

<template>
<input :value="modelValue" @input="onInput" />
</template>

<!-- 使用自定义修饰符 -->
<MyInput v-model.capitalize="text" />

5. 插槽 (Slots)

默认插槽

<!-- FancyButton.vue -->
<template>
  <button class="fancy-btn">
    <slot>默认按钮文字</slot>
  </button>
</template>
<!-- 使用 -->
<FancyButton>
  <span>点击我</span>
</FancyButton>

具名插槽

<!-- BaseLayout.vue -->
<template>
  <div class="container">
    <header>
      <slot name="header">默认头部</slot>
    </header>
    <main>
      <slot>默认内容</slot>
    </main>
    <footer>
      <slot name="footer">默认底部</slot>
    </footer>
  </div>
</template>
<!-- 使用 -->
<BaseLayout>
  <template #header>
    <h1>页面标题</h1>
  </template>

<p>主要内容区域</p>

<template #footer>
<p>版权信息</p>
</template>
</BaseLayout>

作用域插槽

<!-- DataList.vue -->
<script setup>
const items = ref([
  { id: 1, name: '项目1', status: 'active' },
  { id: 2, name: '项目2', status: 'inactive' }
])
</script>

<template>
<ul>
<li v-for="item in items" :key="item.id">
<slot :item="item" :index="items.indexOf(item)">
{{ item.name }}
</slot>
</li>
</ul>
</template>

<!-- 使用 -->
<DataList v-slot="{ item, index }">
  <span>{{ index + 1 }}. {{ item.name }} - {{ item.status }}</span>
</DataList>

6. 依赖注入 (Provide / Inject)

父组件提供

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

const theme = ref('light')
const toggleTheme = () => {
theme.value = theme.value === 'light' ? 'dark' : 'light'
}

provide('theme', {
current: theme,
toggle: toggleTheme
})
</script>

子组件注入

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

const theme = inject('theme')

// 提供默认值
const config = inject('config', { lang: 'zh' })

// 使用 Symbol 作为 key
import { THEME_KEY } from './keys'
const themeData = inject(THEME_KEY)
</script>

<template>
<div :class="theme.current.value">
<button @click="theme.toggle">切换主题</button>
</div>
</template>

7. 动态组件

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

const current = shallowRef(ComponentA)

function toggle() {
current.value = current.value === ComponentA ? ComponentB : ComponentA
}
</script>

<template>
<button @click="toggle">切换组件</button>

<!-- 使用 <component :is="..."> -->
<component :is="current" />

<!-- 保持状态 -->
<keep-alive>
<component :is="current" />
</keep-alive>
</template>

8. 异步组件

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

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

// 高级配置
const AsyncComponentWithOptions = defineAsyncComponent({
loader: () => import('./AsyncComponent.vue'),
loadingComponent: LoadingComponent, // 加载中显示的组件
errorComponent: ErrorComponent, // 加载失败显示的组件
delay: 200, // 延迟显示 loading
timeout: 3000 // 超时时间
})
</script>

<template>
<AsyncComponent />
</template>

9. 组件通信总结

| 方式 | 方向 | 场景 | |------|------|------| | Props | 父 → 子 | 传递数据 | | Emits | 子 → 父 | 触发事件 | | v-model | 双向 | 表单组件 | | Provide/Inject | 祖先 → 后代 | 跨层级传递 | | Slots | 父 → 子 | 内容分发 | | 状态管理 | 全局 | 复杂应用 |

小结

Vue 组件系统是构建大型应用的基础:

  • Props/Emits 实现父子组件通信
  • v-model 实现双向绑定
  • Slots 实现内容分发和组件组合
  • Provide/Inject 实现跨层级通信
  • 动态组件 实现组件切换
  • 异步组件 实现代码分割和懒加载
posted @ 2026-06-16 13:06  心梦EGO  阅读(3)  评论(0)    收藏  举报