当前位置: 首页 > article >正文

基于Vue3组件封装的技巧分享

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

  1. 需求

    需求背景

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

    错误示范

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

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

<CustomModal ref="xxxModal" title="xxx" @ok="onXxx" @cancel="onXxx" >content</CustomModal>
  1. $attrs

    问题来了:一切看起来都挺正常。直到有一天同事说:我想要去掉右上角的关闭按钮,能改成自定义的吗
    简单,直接加!
<!-- 省略不相关代码 -->
<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,还会默认绑定至根元素上。这一点需要注意

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

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

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

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

  1. $slots

    问题来了:插槽怎么办,例如a-modal就提供了许多插槽,是不是要用哪个就先在自定义组件上写好呢?
    错误示例
<a-modal>
    <!-- default -->
    <slot></slot>

    <!-- title -->
    <template #title>
<slot name="title">{{ title }}</slot>
    </template>

    <!-- other -->
</a-modal>

弊端就像之前的,如果该原生提供了许多插槽,当有需要时岂不是频繁去修改自定义组件添加相应的插槽,其实利用$slots可以解决这个问题
在这里插入图片描述
官网的这段话简明扼要的说出的插槽的原理,我们所传递的插槽最终都是变成

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

也就是我们传什么插槽进来,$slots就有什么值那么我们可以遍历$slots中的值,有什么插槽我们便动态绑定什么插槽

<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怎么使用插槽,自定义组件就怎么使用插槽

<CustomModal>
    <template #title="{arg1, arg2}">
        content
    </template>
</CustomModal>

至此,封装的代码如下

<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中也可以参考官网的做法这样子写

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

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

<CustomModal v-model:visible="visible"></CustomModal>

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


http://www.kler.cn/news/325262.html

相关文章:

  • 基于PHP+MySQL组合开发地方门户分类信息网站源码系统 带完整的安装代码包以及搭建部署教程
  • 【数据结构-栈】力扣1441. 用栈操作构建数组
  • Linux防火墙-nat表
  • 828华为云征文 | 使用 Memtester 对华为云 X 实例进行内存性能测试
  • 深入探讨AI 神经网络:类型、特点与创新应用
  • AGI interior designer丨OPENAIGC开发者大赛高校组AI创作力奖
  • C++【类和对象】(取地址运算符重载与实现Date类)
  • 无人机之物流货运篇
  • PDCA优化任务流程
  • OpenCV图像文件读写(2) 检查 OpenCV 是否支持某种图像格式的写入功能函数haveImageWriter()的使用
  • 画个心,写个花!Python Turtle库带你玩转创意绘图!
  • bluefs _flush_range allocated: osd用空间但是显示ceph_bluefs_db_used_bytes is 100%
  • 【国庆要来了】基于Leaflet的旅游路线WebGIS可视化实践
  • 240924-通过服务器代理ip地址及port端口wget等下载文件
  • 如何判断IP有没有被污染过
  • 产品管理 - 互联网产品(3) : 迭代管理
  • 小米笔记本电脑笔记
  • es7.13.2请求体过大
  • java8:处理数据stream并传值
  • 瑞芯微RK3566鸿蒙开发板Android11修改第三方输入法为默认输入法
  • pysim-1
  • [Redis][集群][上]详细讲解
  • ComfyUI 速度更快,显存占用更低的图像反推模型Florence2PromptGen,效果媲美JoyCaption,还支持Flux训练打标
  • Linux驱动开发(速记版)--驱动基础
  • 2024重生之回溯数据结构与算法系列学习(9)【无论是王道考研人还是IKUN都能包会的;不然别给我家鸽鸽丢脸好嘛?】
  • 单ISP与双ISP的区别是什么
  • 踩坑集之demosaic对接VDMA
  • 第三十八条:使用接口模拟可扩展的枚举
  • Vue 学习
  • unity安装报错问题记录