基于vue3组件封装的一些思考

本文在Vue3的基础上针对一些常见UI组件库组件进行二次封装,旨在追求更好的个性化,更灵活的拓展,提供一些个人的思路见解,如有不妥之处,敬请指出。核心知识点$attrs,$slots

1、需求说明

需求背景 :日常开发中,我们经常会使用一些UI组件库诸如and design vue、element plus等辅助开发,提升效率。有时我们需要进行个性化封装,以满足在项目中大量使用的需求。

错误示范 :基于a-modal封装一个自定义Modal组件:修改modal样式,按钮样式、每次关闭后销毁、渲染到指定元素上等等,后续项目的弹窗全部基于该自定义组件。

xml 复制代码
<template>
    <div ref="myModal" class="custom-modal"></div>
    <a-modal
        v-model:visible="visible"
        centered
        destroyOnClose
        :getContainer="() => $refs.myModal"
        @ok="handleOk"
        @cancel="handleCancel"
        :style="{ width: '560px', ...style }"
        :cancelText="cancelText"
        :okText="okText"
    >
        <!-- 以上皆为该组件的默认属性 -->
        <slot></slot>
    </a-modal>
</template>
​
<script setup>
const props = defineProps({
    title: {
        type: String,
        default: "",
    },
    style: {
        type: Object,
        default: () => ({}),
    },
    cancelText: {
        type: String,
        default: "取消",
    },
    okText: {
        type: String,
        default: "确定",
    },
});
const emits = defineEmits(["handleOk", "handleCancel"]);
const visible = ref(false);
​
const handleOk = () => {
    emits("handleOk");
};
const handleCancel = () => {
    emits("handleCancel");
};
defineExpose({ visible });
</script>
​
<style lang="less" scoped>
.custom-modal {
    :deep(.ant-modal) {
        //省略几百行样式代码
    }
}
</style>

代码封装完成,于是乎我们便能在项目中应用带有项目风格的弹窗

perl 复制代码
<CustomModal ref="xxxModal" title="xxx" @ok="onXxx" @cancel="onXxx" >content</CustomModal>

2、$attrs

问题来了 :一切看起来都挺正常。直到有一天同事说:我想要去掉右上角的关闭按钮,能改成自定义的吗

简单,直接加!

xml 复制代码
 <!-- 省略不相关代码 -->
<a-modal :closable="closable"></a-modal>
<script setup>
const props = defineProps({
    //...
    closable:{
        type: Boolean,
        default: false
    }
});
</script>

另一位同事说:我不想让它是居中的,能改成自定义的吗,还有一位同事说...

思考 :这样的情况多了,就有点难顶。每次一有新的需求,我就得改这个组件,导致这个组件代码越来越冗余。那么是否有一种方式能够将传进来的属性自动绑定给a-modal呢 ,有,那儿就是attrs

注意:

1.vue提供了$attrs这么一个属性用于接收父组件传递下来的属性,$attrs不包括已经写入props的值

2.如果父组件传递了style,class,那么这这些值不仅会存在于$attrs,还会默认绑定至根元素上。这一点需要注意

ruby 复制代码
<modalTest :footer="null" :centered="false" :zIndex="999" />
//此时的$attrs
{ "footer": null, "centered": false, "zIndex": 999 }

有了这个组件实例,结合v-bind我们就可以这么写

xml 复制代码
    <a-modal
        v-model:visible="visible"
        centered
        destroyOnClose
        :getContainer="() => $refs.myModal"
        :style="{ width: '560px', ...style }"
        v-bind="$attrs"
    >
     <!-- 略  -->
    </a-modal>

这样一来,我们就可以使用a-modal提供的任意属性和方法了

3、$slots

问题来了 :插槽怎么办,例如a-modal就提供了许多插槽,是不是要用哪个就先在自定义组件上写好呢

错误示例:

xml 复制代码
<a-modal>
    <!-- default -->
    <slot></slot>
​
    <!-- title -->
    <template #title>
<slot name="title">{{ title }}</slot>
    </template>
​
    <!-- other -->
</a-modal>

弊端就像之前的,如果该原生提供了许多插槽,当有需要时岂不是频繁去修改自定义组件添加相应的插槽

其实利用$slots可以解决这个问题

官网的这段话简明扼要的说出的插槽的原理,我们所传递的插槽最终都是变成

arduino 复制代码
{
    'slotName': fn(...args)  //fn返回一个虚拟DOM
    'defautl': fn(...args) //默认插槽
}

也就是我们传什么插槽进来,$slots就有什么值

那么我们可以遍历$slots中的值,有什么插槽我们便动态绑定什么插槽

xml 复制代码
<a-modal>
    <template v-for="(_val, name) in $slots" #[name]="options">
        <slot :name="name" v-bind="options || {}"> </slot>
    </template>
</a-modal>

#[name]="options",我们可以拿到原生a-modalname这个插槽中传递来的一些状态options,并绑定在<slot>上。详情请查看官网:作用域插槽

这样一来,我们原生a-modal怎么使用插槽,自定义组件就怎么使用插槽

xml 复制代码
<CustomModal>
    <template #title="{arg1, arg2}">
        content
    </template>
</CustomModal>

至此,封装的代码如下

ini 复制代码
<template>
    <div ref="myModal" class="custom-modal"></div>
    <a-modal
        v-model:visible="visible"
        centered
        :getContainer="() => $refs.myModal"
        :style="{ width: '560px'}"
        destroyOnClose
        v-bind="$attrs"
    >
        <template v-for="(_val, name) in $slots" #[name]="ops">
            <slot :name="name" v-bind="ops || {}"> </slot>
        </template>
    </a-modal>
</template>
​
<script setup>
const visible = ref(false);
defineExpose({ visible });
</script>
​
<style lang="less" scoped>
.custom-modal {
    //style
}
</style>

还有许多优化的空间,例如当前父组件显隐该Modal需使用ref的方式访问visible。在vue3中也可以参考官网的做法这样子写

xml 复制代码
<template>
    <div ref="myModal" class="custom-modal"></div>
    <a-modal
        :visible="visible"
        //....
        v-bind="$attrs"
    >
          <!-- ...  -->
    </a-modal>
</template>
​
<script setup>
defineProps(['visible'])
const emit = defineEmits(); // 不用写"update:visible",vue会自动加上
watch(
    () => props.visible,
    (newVal) => emit("update:visible", newVal);
);
</script>

那么使用这个控制这个组件的显示隐藏就方便许多了

ini 复制代码
<CustomModal v-model:visible="visible"></CustomModal>

本文中的封装方式是基于vue3来进行实现,不局限在什么UI组件身上。如果使用的是vue2,情况稍有不同,请自行了解。

相关推荐
恋猫de小郭44 分钟前
Flutter Zero 是什么?它的出现有什么意义?为什么你需要了解下?
android·前端·flutter
崔庆才丨静觅7 小时前
hCaptcha 验证码图像识别 API 对接教程
前端
passerby60618 小时前
完成前端时间处理的另一块版图
前端·github·web components
掘了8 小时前
「2025 年终总结」在所有失去的人中,我最怀念我自己
前端·后端·年终总结
崔庆才丨静觅8 小时前
实用免费的 Short URL 短链接 API 对接说明
前端
崔庆才丨静觅9 小时前
5分钟快速搭建 AI 平台并用它赚钱!
前端
崔庆才丨静觅9 小时前
比官方便宜一半以上!Midjourney API 申请及使用
前端
Moment9 小时前
富文本编辑器在 AI 时代为什么这么受欢迎
前端·javascript·后端
崔庆才丨静觅9 小时前
刷屏全网的“nano-banana”API接入指南!0.1元/张量产高清创意图,开发者必藏
前端
剪刀石头布啊9 小时前
jwt介绍
前端