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 实现跨层级通信
- 动态组件 实现组件切换
- 异步组件 实现代码分割和懒加载

浙公网安备 33010602011771号