[vue3] 组件封装技巧

一、前言

文章包含我对封装的一些理解,并提供几种技巧和封装实例,包括弹窗(抽屉)的封装套路、Tooltip根据父元素宽度自动显示、按钮组的封装。

二、技巧

封装在我看来有两种:

  1. 页面上重复了,我封装方便一改多,这种一般和业务强耦合
  2. 对原有组件的增强。比如给组件库的某一组件,再包裹封装一层,以统一样式风格、补充功能

对象prop处理:

  1. 对象内部的属性声明必不可少,不然别人无法理解要传什么
  2. 使用watch或watchEffect监听对象prop的变化,更新到组件内部对象中
  3. 由于对象是引用类型,所以赋值时需要拷贝,否则会改变原始prop,不符合单向数据流的思想

v-bind="$attrs" 直接绑定所有透传属性,不用在一个个声明

弹窗或抽屉组件的显示隐藏通过方法来控制而不是prop,这样更好维护(调用组件不用管显示状态)

按钮组的封装:优先插槽,因为扩展性最强,其次通过传递按钮列表控制

怎么调用内部组件的方法?

 

TS

复制代码

<el-table ref="tableRef"> // 向外暴露el-table实例,以调用它的内部方法 defineExpose({ getTableInstance: () => tableRef.value, })

三、表单弹窗

通过这个例子演示弹窗(抽屉)的封装套路,思路都是一样的。

(1)封装

 

TS

复制代码

<template> <a-modal title="表单弹窗" v-model:open="formModal.open" @ok="formModal.handleConfirm" @cancel="formModal.closeModal" > <a-form ref="formRef" :model="formModal.form" :rules="formModal.rules"> <a-form-item label="名称" name="name"> <a-input v-model:value="formModal.form.name" /> </a-form-item> <a-form-item label="描述" name="desc"> <a-textarea v-model:value="formModal.form.desc" /> </a-form-item> </a-form> </a-modal> </template> <script lang="ts" setup> const props = defineProps(['form']); const emit = defineEmits(['confirm']); const formRef = ref(); const formModal = reactive({ open: false, form: { name: '', desc: '', }, rules: { name: [{ required: true, message: '名称不能为空', trigger: 'change' }], desc: [{ required: true, message: '请输入描述', trigger: 'blur' }], }, showModal: () => { formModal.open = true; }, closeModal: () => { formModal.open = false; /** * resetFields 无法更新初始值,只能重置为第一次传入的 form * 解决方法:<a-form v-if="formModal.open" ... */ formRef.value.resetFields(); }, handleConfirm: () => { formRef.value .validate() .then(() => { emit('confirm', formModal.form); }) .catch((error) => { console.log('error', error); }); }, }); /** 跟踪 props.form 的变化,更新 formModal.form */ watchEffect(() => { /** * Object.assign 是浅拷贝,只复制第一层,引用类型还是复制原来的引用 * JSON.parse(JSON.stringify()) 是深拷贝,但注意:undefined 和 function 会被过滤、Date 会转字符串 */ Object.assign(formModal.form, props.form); }); defineExpose({ showModal: formModal.showModal, closeModal: formModal.closeModal, }); </script> <style lang="scss" scoped></style> <style lang="scss"></style>

(2)使用

 

TS

复制代码

<template> <div> <a-button type="primary" @click="formModalRef?.showModal">打开弹窗</a-button> <FormModal ref="formModalRef" :form="formModal.form" @confirm="formModal.handleConfirm" /> </div> </template> <script lang="ts" setup> import FormModal from './FormModal.vue'; /** 通过调用方法来控制弹窗的显示隐藏 */ const formModalRef = ref<{ showModal: () => void; closeModal: () => void }>(); const formModal = reactive({ /** 表单的初始数据 */ form: { name: '123', desc: '', }, /** 提交表单 */ handleConfirm: (newForm) => { console.log(newForm); // formModalRef.value.closeModal(); }, }); </script>

四、Tooltip增强:根据父元素宽度自动显示

用于表格的单元格中,可以仅在内容溢出时显示Tooltip。

(1)封装

 

TS

复制代码

<template> <a-tooltip v-bind="$attrs" v-if="showTooltip || !auto" color="rgba(9, 30, 66, 0.7)" overlayClassName="yc-tooltip"> <template #title> <slot name="title"></slot> </template> <div class="single-line-ellipsis"> <span> <slot></slot> </span> </div> </a-tooltip> <div v-else class="single-line-ellipsis" @mouseenter="handleMouseenter"> <span ref="contentRef"> <slot></slot> </span> </div> </template> <script lang="ts" setup> import { ref } from 'vue'; const props = withDefaults( defineProps<{ auto?: boolean; }>(), { auto: false, // 是否根据父元素宽度自动显示,常用于单元格内 }, ); const showTooltip = ref(false); const contentRef = ref<HTMLElement>(); // 悬浮处理:判断内容宽度是否超过父元素宽度,如果超过则显示Tooltip const handleMouseenter = () => { if (!contentRef.value || !contentRef.value.parentNode || !props.auto) return; const contentWidth = contentRef.value.offsetWidth; const parentWidth = (contentRef.value.parentNode as HTMLElement).offsetWidth; showTooltip.value = contentWidth > parentWidth; }; </script> <style lang="scss"> .yc-tooltip { font-size: 14px; max-width: 300px; .ant-tooltip-inner { max-height: 98px; // 正好四行,超过可以滚动 overflow: auto; } } .single-line-ellipsis { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; word-break: break-all; } </style>

(2)使用

 

TS

复制代码

<YcTooltip auto>     <template #title>测试</template>     <span>123</span> </XTooltip>

五、按钮组:传递按钮列表

 

TS

复制代码

// 伪代码,参考思路即可 ================================================== <Card :btnList="btnList" /> btnList = [{ type: '', label: '查看', event: 'handleView' },{ type: 'danger', label: '删除', event: 'handleDelete' },{ type: 'primary', label: '下载', event: 'handleDownload' }] <el-button v-for="item in btnList" :key="item.label" :type="item.type" @click="callback(item.event)"> {{ item.label }} </el-button> const emit = defineEmits(['handleExamine', 'handleDelete', 'handleDownload','handleAdd']) const callback = (event) => { emit(`${event}`); }

六、最后

封装是件耗时的事情,往往比你想的要复杂,所以如果时间不够,就不封装直接复制!

如果帮到你了,可以点个赞,欢迎交流沟通。


原文链接:https://juejin.cn/post/7375152719483420706
 

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值