组件是 Vue 应用的核心构建块,本质就是可复用的 Vue 实例,有独立的模板、逻辑和样式,能将复杂页面拆分为独立、可维护的小块,如按钮、卡片、轮播图等,我总结了这个Vue 组件的用法。
一、组件核心特性
| 特性 | 说明 |
|---|---|
| 封装性 | 组件内部的模板、逻辑、样式独立,对外暴露统一接口(Props / 事件) |
| 复用性 | 一个组件可在多个页面 / 场景中重复使用(如通用按钮、表单输入框) |
| 组合性 | 组件可嵌套(父组件包含子组件),形成组件树(对应 Vue 应用的 DOM 树) |
| 独立性 | 组件拥有独立的作用域,数据、方法互不干扰(除非显式通信) |
二、组件的创建与注册(Vue3 主流写法)
Vue3 中组件创建分「选项式 API」和「组合式 API(<script setup>)」,后者是推荐写法(更简洁、高效)。
1. 基础组件结构(<script setup>)
vue
<!-- components/MyButton.vue(通用按钮组件) -->
<template>
<!-- 组件模板:只能有一个根节点(Vue3 可多个,推荐单根) -->
<button
class="my-button"
:class="[{ 'btn-primary': type === 'primary' }, size]"
@click="$emit('click')"
>
<!-- 插槽:父组件传递内容 -->
<slot>默认按钮</slot>
</button>
</template>
<script setup>
// 1. 声明 Props(对外接收的参数)
const props = defineProps({
type: {
type: String,
default: 'default' // default/primary/danger
},
size: {
type: String,
default: 'medium' // small/medium/large
}
});
// 2. 声明自定义事件(对外暴露的事件)
const emit = defineEmits(['click']);
// 3. 组件内部逻辑(仅组件内可见)
const handleInternal = () => {
console.log('组件内部方法');
};
</script>
<style scoped>
/* scoped:样式仅作用于当前组件,避免污染 */
.my-button {
border: none;
border-radius: 4px;
padding: 8px 16px;
cursor: pointer;
}
.btn-primary {
background: #409eff;
color: #fff;
}
.small {
padding: 4px 8px;
font-size: 12px;
}
.medium {
padding: 8px 16px;
font-size: 14px;
}
.large {
padding: 12px 24px;
font-size: 16px;
}
</style>
2. 组件注册(局部 / 全局)
(1)局部注册(推荐,按需加载)
在使用组件的父组件中导入并注册(Vue3 <script setup> 中导入即注册):
vue
<!-- Parent.vue -->
<template>
<div>
<!-- 使用自定义组件 -->
<MyButton type="primary" size="large" @click="handleBtnClick">
主要按钮
</MyButton>
<MyButton type="default" size="small">默认按钮</MyButton>
</div>
</template>
<script setup>
// 导入即局部注册(Vue3 语法糖)
import MyButton from './components/MyButton.vue';
const handleBtnClick = () => {
console.log('按钮被点击');
};
</script>
(2)全局注册(适合高频复用组件)
在 main.js 中注册,全局所有组件可直接使用(无需导入):
js
// main.js(Vue3)
import { createApp } from 'vue';
import App from './App.vue';
import MyButton from './components/MyButton.vue';
const app = createApp(App);
// 全局注册组件
app.component('MyButton', MyButton);
app.mount('#app');
3. Vue2 对比(选项式 API)
vue
<!-- Vue2 组件写法 -->
<template>
<button class="my-button" @click="handleClick">
<slot></slot>
</button>
</template>
<script>
export default {
// 组件名
name: 'MyButton',
// 接收参数
props: {
type: {
type: String,
default: 'default'
}
},
// 方法
methods: {
handleClick() {
this.$emit('click');
}
}
};
</script>
<style scoped>
/* 样式同 Vue3 */
</style>
三、组件通信(核心)
组件间通信是组件使用的核心,不同关系的组件通信方式不同:
| 组件关系 | 推荐通信方式 | 示例场景 |
|---|---|---|
| 父 → 子 | Props | 父组件给子组件传按钮类型、文本 |
| 子 → 父 | 自定义事件(emit) | 子组件通知父组件按钮被点击 |
| 父子双向绑定 | v-model(Props + update:xxx 事件) | 表单组件双向绑定值 |
| 跨级 / 非父子 | Provide/Inject 或 Pinia/Vuex | 全局主题、用户信息共享 |
| 任意组件 | 事件总线(mitt)或 Pinia | 非关联组件通知更新 |
1. 父 → 子:Props
vue
<!-- 子组件 Child.vue -->
<script setup>
// 声明接收的 Props
const props = defineProps({
msg: String,
count: {
type: Number,
default: 0
},
user: {
type: Object,
default: () => ({ name: '默认' }) // 对象默认值用函数返回
}
});
// 访问 Props
console.log(props.msg);
</script>
<!-- 父组件 Parent.vue -->
<template>
<Child :msg="parentMsg" :count="10" :user="parentUser" />
</template>
<script setup>
import { ref, reactive } from 'vue';
const parentMsg = ref('父组件传递的消息');
const parentUser = reactive({ name: '张三' });
</script>
2. 子 → 父:自定义事件
vue
<!-- 子组件 Child.vue -->
<template>
<button @click="handleEmit">触发事件</button>
</template>
<script setup>
// 声明事件
const emit = defineEmits(['custom-event']);
const handleEmit = () => {
// 触发事件并传递参数
emit('custom-event', 666, '额外参数');
};
</script>
<!-- 父组件 Parent.vue -->
<template>
<Child @custom-event="handleEvent" />
</template>
<script setup>
const handleEvent = (num, str) => {
console.log('接收子组件参数:', num, str); // 666 额外参数
};
</script>
3. 跨级通信:Provide/Inject
父组件提供数据,所有子孙组件可直接注入使用(无需逐层传递 Props):
vue
<!-- 父组件(顶层) -->
<script setup>
import { provide, ref } from 'vue';
// 提供数据(key + value)
const theme = ref('dark');
provide('theme', theme);
// 提供方法
provide('updateTheme', (newTheme) => {
theme.value = newTheme;
});
</script>
<!-- 孙组件(深层) -->
<script setup>
import { inject } from 'vue';
// 注入数据
const theme = inject('theme', 'light'); // 第二个参数是默认值
// 注入方法
const updateTheme = inject('updateTheme');
// 使用
const changeTheme = () => {
updateTheme('light');
};
</script>
4. 全局通信:Pinia(Vue3 推荐)
js
// stores/theme.js
import { defineStore } from 'pinia';
export const useThemeStore = defineStore('theme', {
state: () => ({
theme: 'dark'
}),
actions: {
updateTheme(newTheme) {
this.theme = newTheme;
}
}
});
// 任意组件使用
<script setup>
import { useThemeStore } from '@/stores/theme';
const themeStore = useThemeStore();
// 读取
console.log(themeStore.theme);
// 修改
themeStore.updateTheme('light');
</script>
四、组件高级特性
1. 插槽(Slot):组件内容分发
插槽允许父组件向子组件传递任意内容(文本、HTML、其他组件),分为:默认插槽、具名插槽、作用域插槽。
(1)默认插槽
vue
<!-- 子组件 -->
<template>
<div class="card">
<slot>默认内容(父组件未传内容时显示)</slot>
</div>
</template>
<!-- 父组件使用 -->
<Card>
<p>自定义卡片内容</p>
</Card>
(2)具名插槽:多区域分发
vue
<!-- 子组件 -->
<template>
<div class="card">
<div class="card-header">
<slot name="header">默认标题</slot>
</div>
<div class="card-body">
<slot>默认内容</slot>
</div>
</div>
</template>
<!-- 父组件使用 -->
<Card>
<template v-slot:header> <!-- 简写 #header -->
<h3>自定义标题</h3>
</template>
<p>卡片主体内容</p>
</Card>
(3)作用域插槽:子传父内容
子组件向插槽传递数据,父组件可接收并渲染:
vue
<!-- 子组件 -->
<template>
<div>
<!-- 传递数据给父组件 -->
<slot :user="user" :age="25"></slot>
</div>
</template>
<script setup>
const user = { name: '张三' };
</script>
<!-- 父组件使用 -->
<Child>
<!-- 接收子组件传递的参数 -->
<template v-slot="slotProps"> <!-- 简写 #default="slotProps" -->
<p>姓名:{{ slotProps.user.name }}</p>
<p>年龄:{{ slotProps.age }}</p>
</template>
</Child>
2. 动态组件:按需切换组件
通过 component 标签和 is 属性,动态渲染不同组件:
vue
<template>
<div>
<button @click="currentComp = 'ComponentA'">显示A</button>
<button @click="currentComp = 'ComponentB'">显示B</button>
<!-- 动态组件 -->
<component :is="currentComp"></component>
</div>
</template>
<script setup>
import { ref } from 'vue';
import ComponentA from './ComponentA.vue';
import ComponentB from './ComponentB.vue';
const currentComp = ref('ComponentA');
</script>
3. 异步组件:懒加载
优化性能,组件仅在需要时加载(如路由组件、低频使用的组件):
vue
<script setup>
import { defineAsyncComponent } from 'vue';
// 异步加载组件
const AsyncComponent = defineAsyncComponent(() => {
return import('./AsyncComponent.vue');
});
// 带加载状态的异步组件
const AsyncComponentWithLoading = defineAsyncComponent({
loader: () => import('./AsyncComponent.vue'),
loadingComponent: () => import('./Loading.vue'), // 加载中显示
errorComponent: () => import('./Error.vue'), // 加载失败显示
delay: 200, // 延迟显示加载组件(毫秒)
timeout: 3000 // 超时时间
});
</script>
<template>
<AsyncComponent />
</template>
4. 组件缓存:keep-alive
缓存组件实例,避免重复创建 / 销毁(如标签页、路由切换):
vue
<template>
<!-- 缓存 ComponentA 和 ComponentB -->
<keep-alive include="ComponentA,ComponentB">
<component :is="currentComp"></component>
</keep-alive>
</template>
include:仅缓存指定组件(组件名);exclude:排除指定组件;max:最大缓存数量,超出则销毁最久未使用的组件。
五、组件设计最佳实践
1. 命名规范
- 组件名:使用 PascalCase(大驼峰,如
MyButton)或 kebab-case(短横线,如my-button),Vue 推荐 PascalCase; - Props 名:使用 camelCase(小驼峰,如
btnSize),父组件传递时可用 kebab-case(btn-size); - 事件名:使用 kebab-case(如
custom-click),避免和原生事件重名。
2. 复用原则
- 单一职责:一个组件只做一件事(如按钮组件只处理按钮逻辑,不包含表单逻辑);
- 可配置化:通过 Props 暴露配置项,避免硬编码(如按钮的类型、大小、禁用状态);
- 低耦合:组件内部逻辑尽量独立,仅通过 Props / 事件和外部交互。
3. 性能优化
- 局部注册:优先局部注册组件,减少全局体积;
- 异步加载:低频组件用异步组件懒加载;
- 缓存组件:频繁切换的组件用
keep-alive缓存; - 避免过度封装:简单逻辑无需拆分为组件,避免增加复杂度。
4. 样式隔离
- 使用
scoped:样式仅作用于当前组件; - 深度选择器:需修改子组件样式时,用
:deep()(Vue3)//deep/(Vue2); - CSS Modules:通过模块化样式避免命名冲突。
六、总结
Vue 组件的核心是「封装、复用、组合」:
- 创建:Vue3 推荐
<script setup>+defineProps/defineEmits,简洁高效; - 注册:局部注册为主,全局注册为辅;
- 通信:父子用 Props / 事件,跨级用 Provide/Inject,全局用 Pinia;
- 高级特性:插槽实现内容分发,动态组件实现按需渲染,异步组件优化性能;
- 设计原则:单一职责、可配置化、低耦合,兼顾复用性和可维护性。
掌握组件的使用和设计,是构建复杂 Vue 应用的基础,合理拆分组件能大幅提升开发效率和代码质量。
675

被折叠的 条评论
为什么被折叠?



