Vue组件二次封装做个安静的透传仔

947 阅读3分钟

二次封装讲解属性透传和封装思路

在 Vue 3 的项目开发中,基于现有组件库进行二次封装是一种非常常见的需求。通过二次封装,我们可以提高代码复用性、统一样式和行为逻辑,并简化业务代码。在这篇文章中,我们将基于 Element Plus 的 el-button 组件进行二次封装。 本文只讲解关键属性的透传和封装思路

为什么需要二次封装

  1. 统一样式:业务场景中需要统一按钮的样式规范,比如默认颜色、大小等。
  2. 行为增强:可以为按钮添加一些通用逻辑,比如防抖、权限控制等。
  3. 提高复用性:减少重复代码,降低维护成本。

开始封装

需求分析

我们希望实现以下功能:

  1. 默认应用一套统一的样式,比如 typesize
  2. 提供加载状态支持,通过 loading 属性控制按钮的加载动画。
  3. 添加点击事件的防抖功能,防止用户多次点击。
  4. 支持通过 v-permission 指令控制按钮的显示(可选)。
  5. 支持自定义插槽内容(包括默认插槽、loading 插槽和 icon 插槽)。

封装代码

基础封装代码

我们可以利用 useAttrsuseSlots 更优雅地捕获和传递属性及插槽。

<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>

核心点解释

  1. useAttrsuseSlots

    • useAttrs 获取传递给组件但未显式声明为 props 的所有属性,方便传递。
    • useSlots 动态捕获插槽,支持自定义内容。
  2. 事件处理

    • 如果需要拦截或增强事件逻辑(如 click 防抖),可以直接操作 attrs 中的事件,例如 attrs.onClick
    • 支持使用 ...args 捕获并传递事件参数,保证灵活性。
  3. 插槽传递

    • 动态插槽:通过 <template v-for="..."> 遍历并动态绑定所有插槽内容。
    • 每个插槽都能自动继承父组件上下文的动态内容。
  4. 减少重复代码

    • 无需手动列举 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 的二次封装,我们可以在项目中更高效地使用按钮组件。封装后的组件不仅可以统一样式,还能通过添加功能提高组件的灵活性。利用 useAttrsslot,我们可以更优雅地处理未声明的属性和动态内容,减少代码冗余,提高组件的可维护性。希望本文的示例能够对你的项目开发有所帮助!