二次封装讲解属性透传和封装思路
在 Vue 3 的项目开发中,基于现有组件库进行二次封装是一种非常常见的需求。通过二次封装,我们可以提高代码复用性、统一样式和行为逻辑,并简化业务代码。在这篇文章中,我们将基于 Element Plus 的 el-button
组件进行二次封装。 本文只讲解关键属性的透传和封装思路
为什么需要二次封装
- 统一样式:业务场景中需要统一按钮的样式规范,比如默认颜色、大小等。
- 行为增强:可以为按钮添加一些通用逻辑,比如防抖、权限控制等。
- 提高复用性:减少重复代码,降低维护成本。
开始封装
需求分析
我们希望实现以下功能:
- 默认应用一套统一的样式,比如
type
、size
。 - 提供加载状态支持,通过
loading
属性控制按钮的加载动画。 - 添加点击事件的防抖功能,防止用户多次点击。
- 支持通过
v-permission
指令控制按钮的显示(可选)。 - 支持自定义插槽内容(包括默认插槽、
loading
插槽和icon
插槽)。
封装代码
基础封装代码
我们可以利用 useAttrs
和 useSlots
更优雅地捕获和传递属性及插槽。
<script setup>
import { useAttrs, useSlots } from 'vue';
import { ElButton } from 'element-plus';
const attrs = useAttrs(); // 捕获未声明的属性
const slots = useSlots(); // 获取传递的插槽内容
// 防抖处理的计时器
let timer = null;
const handleClick = (...args) => {
const debounce = attrs.debounce || 0; // 支持防抖逻辑
if (debounce > 0) {
if (timer) clearTimeout(timer);
timer = setTimeout(() => {
timer = null;
attrs.onClick && attrs.onClick(...args); // 传递事件参数
}, debounce);
} else {
attrs.onClick && attrs.onClick(...args);
}
};
</script>
<template>
<el-button v-bind="attrs" @click="handleClick">
<template v-for="(slotContent, slotName, i) in slots" :key="i" v-slot:[slotName]>
<slot :name="slotName" />
</template>
</el-button>
</template>
核心点解释
-
useAttrs
和useSlots
useAttrs
获取传递给组件但未显式声明为props
的所有属性,方便传递。useSlots
动态捕获插槽,支持自定义内容。
-
事件处理
- 如果需要拦截或增强事件逻辑(如
click
防抖),可以直接操作attrs
中的事件,例如attrs.onClick
。 - 支持使用
...args
捕获并传递事件参数,保证灵活性。
- 如果需要拦截或增强事件逻辑(如
-
插槽传递
- 动态插槽:通过
<template v-for="...">
遍历并动态绑定所有插槽内容。 - 每个插槽都能自动继承父组件上下文的动态内容。
- 动态插槽:通过
-
减少重复代码
- 无需手动列举
props
和绑定逻辑,组件可以自动支持el-button
的所有属性和事件。
- 无需手动列举
增加插槽支持
Element Plus 的 el-button
支持多个插槽,比如:
- default:默认插槽内容。
- loading:自定义加载中组件。
- icon:自定义图标组件。
我们通过动态绑定 slot
来支持这些内容:
<template>
<el-button v-bind="attrs" @click="handleClick">
<template v-for="(slotContent, slotName, i) in slots" :key="i" v-slot:[slotName]>
<slot :name="slotName" />
</template>
</el-button>
</template>
这段代码支持所有插槽类型,无需显式声明名称,保证了组件的通用性。
增加权限控制
如果项目中需要对按钮进行权限控制,可以配合 v-permission
指令实现。假设我们已有一个全局指令 v-permission
:
// permission.js
export default {
mounted(el, binding) {
const hasPermission = checkUserPermission(binding.value); // 权限校验逻辑
if (!hasPermission) {
el.style.display = 'none';
}
},
};
function checkUserPermission(permission) {
// 模拟权限校验逻辑
const userPermissions = ['read', 'write'];
return userPermissions.includes(permission);
}
在按钮封装中,可以这样使用:
<template>
<el-button
v-permission="permission"
v-bind="attrs"
@click="handleClick"
>
<template v-for="(slotContent, slotName, i) in slots" :key="i" v-slot:[slotName]>
<slot :name="slotName" />
</template>
</el-button>
</template>
<script setup>
const props = defineProps({
permission: {
type: String,
default: '',
},
});
</script>
使用封装组件
在业务代码中,可以直接使用我们封装好的组件:
<template>
<MyButton
type="success"
icon="el-icon-check"
:loading="isLoading"
round
debounce="300"
@click="handleSubmit"
>
提交
</MyButton>
<!-- 插槽动态内容 -->
<MyButton
type="primary"
:loading="isLoading"
@click="handleCustom"
>
<template #default="slotProps">
<span>{{ slotProps.type }} 按钮</span>
</template>
<template #loading>
<el-icon-loading />
</template>
<template #icon>
<el-icon-user />
</template>
</MyButton>
</template>
<script setup>
import MyButton from '@/components/MyButton.vue';
import { ref } from 'vue';
const isLoading = ref(false);
const handleSubmit = async () => {
isLoading.value = true;
// 模拟异步操作
await new Promise((resolve) => setTimeout(resolve, 2000));
isLoading.value = false;
};
const handleCustom = () => {
console.log('自定义按钮点击事件');
};
</script>
总结
通过对 el-button
的二次封装,我们可以在项目中更高效地使用按钮组件。封装后的组件不仅可以统一样式,还能通过添加功能提高组件的灵活性。利用 useAttrs
和 slot
,我们可以更优雅地处理未声明的属性和动态内容,减少代码冗余,提高组件的可维护性。希望本文的示例能够对你的项目开发有所帮助!