前端基础向--从项目入手封装公共组件

news2024/12/26 4:44:18

本文就从 “详情卡片” 业务组件的封装的几个阶段来说明我在编写公共组件的设计思路。

1. 阶段一:基础需求

假设我们现在有这样一个需求:需要满足显示产品的详细信息;需要可以根据不同分辨率适配不同的显示方式(2列,3列,4列)。基础样式如下:

为了快速实现不同分辨率实现不同的列数,最先想到的就是使用 UI 库的布局组件,这里我们选择的是 ElementUI 的 Row/Col 组件。

在满足当前的需求下的代码如下:

<script>
export default {name: "DetailPageInfoContent",props: {details: {type: Object,default: () => ({})},sizes: {type: Object,default: () => ({ xs: 12, sm: 12, md: 12, lg: 8, xl: 6 })},prop: {type: Object,default: () => ({})}},computed: {detailInfoList() {if (!this.details) return [];return Object.keys(this.prop).reduce((list, propKey, index) => {let label, value;label = this.prop[propKey];value = (this.details[propKey] || "-").toString();list.push({ index, label, value });return list;}, []);}},render(h) {return h("div", { class: "detail-page-info-content" }, [h("el-row", { props: { gutter: 16 } }, [this.detailInfoList.map((item) =>h("el-col",{props: { ...this.sizes },class: "info-item-content",key: item.index},[h("div", { class: "info-content_label" }, item.label),item.value.length < 32? h("div", { class: "info-content_value" }, item.value): h("el-tooltip", { props: { content: item.value } }, [h("div", { class: "info-content_value" }, item.value)])]))])]);}
};
</script>

<style lang="scss"> .detail-page-info-content {box-sizing: border-box;
}
.info-item-content {font-size: 0.8rem;line-height: 32px;display: flex;align-items: center;color: $--font-color-secondary;
}
.info-content_value {flex: 1;overflow: hidden;word-break: break-word;text-overflow: ellipsis;white-space: nowrap;color: $--font-color-base;
} </style> 

此时的组件仅仅满足传入一个配置对象 prop 和详情数据 details,以及一个默认的 ElRow 的布局配置。在 Value 长度大于 32 时使用 Tooltip 来显示超长的信息部分。

2. 阶段二:插槽

上面的组件仅仅满足已经符合正确格式的 prop 配置和 details,但是在需要增加图片预览按钮、操作按钮等情况时,则无法满足。

所以可以增加两个插槽,用在最前面和最末尾显示扩展信息。

<script>
export default {name: "DetailPageInfoContent",props: {},computed: {detailInfoList() {if (!this.details) return [];return Object.keys(this.prop).reduce((list, propKey, index) => {let label, value;label = this.prop[propKey];value = (this.details[propKey] || "-").toString();list.push({ index, label, value });return list;}, []);}},render(h) {return h("div", { class: "detail-page-info-content" }, [this.$scopedSlots.append && this.$scopedSlots.append(),h("el-row", { props: { gutter: 16 } }, [this.detailInfoList.map((item) =>h("el-col",{props: { ...this.sizes },class: "info-item-content",key: item.index},[h("div", { class: "info-content_label" }, item.label),item.value.length < 32? h("div", { class: "info-content_value" }, item.value): h("el-tooltip", { props: { content: item.value } }, [h("div", { class: "info-content_value" }, item.value)])])),this.$scopedSlots.prepend && this.$scopedSlots.prepend()])]);}
};
</script> 

此时开发者可以在外面通过插槽的形式注入一个 ElCol 组件或者其他组件,用来显示自定义的内容。

但是这时还有缺点:不能使用函数 format 转换需要显示的值,这种情况经常出现在处理字典值、时间日期等时候。

3. 阶段三:接收转换函数

这里的转换函数一般都用于单独的一个字段的处理,所以统一放在 prop 中。

<script>
export default {name: "DetailPageInfoContent",props: {},computed: {detailInfoList() {if (!this.details) return [];return Object.keys(this.prop).reduce((list, propKey, index) => {let label, value;if (typeof this.prop[propKey] === "string") {label = this.prop[propKey];value = (this.details[propKey] || "-").toString();} else {label = this.prop[propKey].label;props = this.prop[propKey].props;const format = this.prop[propKey].format;value =format && typeof format === "function"? (format(propKey, this.details[propKey]) || "-").toString(): (this.details[propKey] || "-").toString();}list.push({ index, label, value, props });return list;}, []);}},render(h) {return h("div", { class: "detail-page-info-content", style: this.computedStyle }, [h("el-row", { props: { gutter: 16 } }, [this.$scopedSlots.append && this.$scopedSlots.append(),this.detailInfoList.map((item) =>h("el-col",{props: { ...this.sizes, ...(item.props || {}) },class: "info-item-content",key: item.index},[h("div", { class: "info-content_label" }, item.label),item.value.length < 32? h("div", { class: "info-content_value" }, item.value): h("el-tooltip", { props: { content: item.value } }, [h("div", { class: "info-content_value" }, item.value)])])),this.$scopedSlots.prepend && this.$scopedSlots.prepend()])]);}
};
</script> 

这里依旧遵循一个原则:改造影响最小化,不影响原有代码逻辑。所以这里在组装数据显示用的数组时,需要判断原有的 prop 配置格式。在单独一个配置项依旧为对象时,才调用其中的转换函数 format 处理显示结果。

另外为每一个显示项目增加了一个 props 配置,用来绑定到 ElCol 组件中,进行特定配置。

4. 阶段四:自定义 render 与配置化

如果上面的几种方式依然不满足情况:需要显示的不止有数据信息,还有其他按钮;需要根据条件隐藏某些显示项;需要增加四周的边距适配不同的页面;不需要显示信息,只需要自定义的显示内容。

所以重新对其进行改造:增加自定义 render 与配置项 onlyRenderhidden 等配置。

<script>
export default {name: "DetailPageInfoContent",props: {details: {type: Object,default: () => ({})},padding: {type: [String, Array],default: "8px 16px"},sizes: {type: Object,default: () => ({ xs: 12, sm: 12, md: 12, lg: 8, xl: 6 })},prop: {type: Object,default: () => ({})},labelPrefix: {type: String,default: ":"}},computed: {detailInfoList() {if (!this.details) return [];return Object.keys(this.prop).reduce((list, propKey, index) => {let label, value, render, hidden, onlyRender, props;if (typeof this.prop[propKey] === "string") {label = this.prop[propKey] + this.labelPrefix;value = (this.details[propKey] || "-").toString();} else {label = this.prop[propKey].label + this.labelPrefix;render = this.prop[propKey].render;hidden = this.prop[propKey].hidden;onlyRender = this.prop[propKey].onlyRender;props = this.prop[propKey].props;const format = this.prop[propKey].format;value =format && typeof format === "function"? (format(propKey, this.details[propKey]) || "-").toString(): (this.details[propKey] || "-").toString();}!hidden && list.push({ index, label, value, render, onlyRender, props });return list;}, []);},computedStyle() {const padding = this.padding;if (typeof this.padding === "object") return { padding: padding.join(" ") };return { padding };}},render(h) {return h("div", { class: "detail-page-info-content", style: this.computedStyle }, [h("el-row", { props: { gutter: 16 } }, [this.$scopedSlots.append && this.$scopedSlots.append(),this.detailInfoList.map((item) =>h("el-col",{props: { ...this.sizes, ...(item.props || {}) },class: "info-item-content",key: item.index},item.onlyRender? [h("div", { class: "info-content_label" }, item.label), item.render && item.render(h, item)]: [h("div", { class: "info-content_label" }, item.label),item.value.length < 32? h("div", { class: "info-content_value" }, item.value): h("el-tooltip", { props: { content: item.value } }, [h("div", { class: "info-content_value" }, item.value)]),item.render && item.render(h, item)])),this.$scopedSlots.prepend && this.$scopedSlots.prepend()])]);}
};
</script> 

此时,我们的组件就可以满足绝大多数详情卡片的显示情况了,并且可以适应不同分辨率下的不同布局。

在使用时,我们只需要进行详情数据的配置,增加相关操作即可。

<detail-page-info-content :details="productDetails" :prop="baseInfoProps"><template #append><el-col :xs="12" :sm="12" :md="12" :lg="8" :xl="6" class="info-item-content"><div class="info-content_label">产品图片:</div><image-preview url="https://fuss10.elemecdn.com/a/3f/3302e58f9a181d2509f3dc0fa68b0jpeg.jpeg" /></el-col>
</template>
</detail-page-info-content>
//...
baseInfoProps: {productId: "产品ID",nodeType: { label: "节点类型", format: getEnumLabel, render: null },protocolType: { label: "接入协议", format: getEnumLabel },reserveProductNo: "接入产品ID",netWorkMethod: { label: "联网方式", format: getEnumLabel },syncState: {label: "同步状态",hidden: !this.isChannelEdition,render: (h) => h("el-button", { props: { type: "text", onClick: this.syncProduct.bind(this) } }, "同步")},connectType: { label: "连接方式", format: getEnumLabel },prdCode: { label: "产品类别", format: this.prdName.bind(this) },placeType: { label: "内外场类型", format: getEnumLabel },cipher: "产品密钥",createDt: "创建时间",updateDt: "修改时间",description: "产品描述"
},
productDetails: {} 

5. 总结

与封装项目工具函数一样,封装项目公共组件常常需要配合项目需求,在结合项目需求与UI规范的情况下,脱离具体业务操作逻辑来封装组件。

在经历多个项目或者产品之后,小组内通常就能根据以往的项目经验与实际情况,封装一套自己内部的“半业务向组件库”。为什么说是“半业务”?

个人总结的就是:组件封装需要结合小组实际项目情况、组件需要脱离业务实际逻辑、组件需要在“最简单的配置使用”下满足常规使用场景、组件需要有较高的自由度支持配置化与二次扩展。

在实际的项目迭代过程中,我们封装的组件或者函数终究很难做到尽善尽美,所以我们能做的就是在封装的时候留下足够的余地,在迭代的过程中做好对原始代码的兼容;并且需要做好代码的可读性。

最后

为大家准备了一个前端资料包。包含54本,2.57G的前端相关电子书,《前端面试宝典(附答案和解析)》,难点、重点知识视频教程(全套)。



有需要的小伙伴,可以点击下方卡片领取,无偿分享

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/4957.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【Linux】进程通信 | 管道

今天让我们来认识如何使用管道来进行进程间通信 文章目录1.何为管道&#xff1f;1.1 管道是进程间通信的一种方式1.2 进程通信1.3 管道分类2.匿名管道2.0 康康源码2.1 创建2.2 父子通信完整代码2.3 等待写入等待读取等待源码中的体现2.4 控制多个子进程2.5 命令行 |3.命名管道3…

linux无界面手敲命令笔记

0 Ubuntu相关命令简介 1. 文件及目录操作命令 pwd&#xff1a;显示用户当前所处的目录 ls&#xff1a;列出目录下的文件清单 cd&#xff1a;改变当前目录cd … 返回上一级目cd / 进入根目录不加参数或参数为“~”&#xff0c;默认切换到用户主目录 mkdir&#xff1a;建立目录 …

Ant Design表单之labelCol 和wrapperCol的实际开发笔记

目录 前言 一、labelCol和wrapperCol是什么 二、布局的栅格化 1.布局的栅格化系统的工作原理 三、栅格常用的属性 1.左右偏移 2.区块间隔 3.栅格排序 四、labelCol和wrapperCol的实际使用 总结 前言 主要是记录一下栅格布局的一些属性和labelCol、wrapperCol等。 一…

[附源码]java毕业设计毕业设计管理系统

项目运行 环境配置&#xff1a; Jdk1.8 Tomcat7.0 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项目技术&#xff1a; SSM mybatis Maven Vue 等等组成&#xff0c;B/S模式 M…

国产AI绘画软件“数画”刷爆朋友圈,网友到底在画什么

人们常说&#xff0c;眼见为实&#xff0c;只有自己亲眼见到的才会相信。但是我们都知道眼睛会产生错觉&#xff0c;而且人们在生活中被错觉误导的情况屡见不鲜。例如图中&#xff0c;你以为她们肯定是真人的照片。世界上有些事情&#xff0c;即使是自己亲眼所见到的也未必一定…

c/c++内存管理

前言&#xff1a; 开篇前就聊聊篮球&#xff0c;在众多球星中&#xff0c;我觉得杜兰特&#xff08;KD&#xff09;非常专注于篮球&#xff0c;他一直坚持他所热爱的事业。尽管有很多缺点&#xff0c;但是他对于篮球的态度是坚定不移&#xff0c;这是我非常钦佩的。当然库里&am…

大数据环境搭建 —— VMware Workstation 安装详细教程

大数据系列文章&#xff1a;&#x1f449; 目录 &#x1f448; 文章目录一、下载安装包1. 下载 VMware Workstation2. 小技巧二、安装软件1. 软件安装2. 虚拟环境搭建一、下载安装包 1. 下载 VMware Workstation ① 打开 VMware Workstation 官方下载网站 VMware Workstati…

【Linux】管理文件和目录的命令大全

目录 Linux 管理文件和目录的命令 1.命令表 2.细分 1.pwd命令 2.cd 命令 3.ls 命令 4.cat 命令 5.grep 命令 6.touch 命令 7.cp 命令 8.mv 命令 9.rm 命令 10.mkdir 命令 11.rmdir 命令 赠语&#xff1a;Even in darkness, it is possible to create light.即使在…

C++构造函数

构造函数详解 类的6个默认的成员函数: 类中如果什么都没有定义:---有六个默认的成员函数: 构造函数:主要完成对象的初始化工作析构函数:主要完成对象中资源的清理工作拷贝构造函数:拷贝一个新的对象赋值运算符重载: 让两个对象之间进行赋值引用的重载:普通和const类型--->…

【Vue】VueCLI 的使用和单文件组件(2)

首先作为一个工程来说&#xff0c; 一般我们的源代码都放在src目录下&#xff1a; 外面的代码我们先不去管它&#xff0c;后面在工程编写的时候再给大家仔细的介绍。‍‍ 这块大家主要知道我们的源代码 都在src里面&#xff0c;它的入口文件是一个man点js文件&#xff0c;‍‍…

【day21】每日一题——MP3光标位置

MP3光标位置_牛客题霸_牛客网 这题就是简单的根据它的规则把它的情况都列举出来即可&#xff08;当然&#xff0c;我第一次写一脸懵逼&#xff0c;所以你现在一脸懵逼没事&#xff0c;看完你就觉得简单了。看完还懵逼&#xff0c;你就多看几遍&#xff0c;然后自己去尝试一下&a…

C/C++,不废话的宏使用技巧

经典废话 下面的所有内容全是我在欣赏一串代码时发出的疑问&#xff0c;之前对宏的了解不多&#xff0c;导致在刚看到下面的这串代码的时候是“地铁 老人 手机”&#xff0c;具体代码如下&#xff0c;如果有对这里解读有问题的欢迎在评论区留言。 一、预定义宏 编译一个程…

在线就能制作活动邀请函,一键生成链接

今天小编教你如何在线制作一个活动邀请函&#xff0c;不需要下载软件&#xff0c;也不需要编程代码&#xff0c;只需使用乔拓云工具在线一键就能生成活动邀请函和邀请函链接&#xff0c;下面就跟着小编的教学开始学习如何在线制作活动邀请函&#xff01;第一步&#xff1a;打开…

[附源码]java毕业设计SSM归途中流浪动物收容与领养管理系统

项目运行 环境配置&#xff1a; Jdk1.8 Tomcat7.0 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项目技术&#xff1a; SSM mybatis Maven Vue 等等组成&#xff0c;B/S模式 M…

OSPF高级配置——虚接口,NSSA

作者介绍&#xff1a; 作者&#xff1a;小刘在C站 每天分享课堂笔记&#xff0c;一起努力&#xff0c;共赴美好人生&#xff01; 夕阳下&#xff0c;是最美的绽放。 目录 一.ospf 虚链路 二.虚链路的目的 三.配置虚链路的规则及特点 四.虚链路的配置&#xff1a; nssa …

HTML小游戏6 —— 《高达战争》横版射击游戏(附完整源码)

&#x1f482; 网站推荐:【神级源码资源网】【摸鱼小游戏】&#x1f91f; 风趣幽默的前端学习课程&#xff1a;&#x1f449;28个案例趣学前端&#x1f485; 想寻找共同学习交流、摸鱼划水的小伙伴&#xff0c;请点击【摸鱼学习交流群】&#x1f4ac; 免费且实用的计算机相关知…

奥密克戎 (Omicron) 知多少m?| MedCheExpress

这个冬天 Omicron 已迅速超越其他变种&#xff0c;成为主要的 SARS-CoV-2 毒株&#xff0c;尽管该变体在体内引起的病毒水平与其“竞争对手” Delta 相比更低&#xff0c;但威力不容小觑。 ■ 第五大变异关注病毒株&#xff0c;有何神奇之处&#xff1f; 2021 年 11 月 24 日&…

深度自定义mybatis

> 回顾mybatis的操作的核心步骤 > > 编写核心类SqlSessionFacotryBuild进行解析配置文件 > 深度分析解析SqlSessionFacotryBuild干的核心工作 > > 编写核心类SqlSessionFacotry > 深度分析解析SqlSessionFacotry干的核心工作 > 编写核心类SqlSession &…

【面试官让我十分钟实现一个链表?一个双向带头循环链表甩给面试官】

我们在面试中面试官一般都会让我们现场写代码&#xff0c;如果你面试的时候面试官让你十分钟写一个链表&#xff0c;你是不是懵逼了&#xff1f;十分钟写一个链表&#xff0c;怎么可能&#xff1f;事实上是有可能的&#xff0c;十分钟写出的链表也能震惊面试官。 我们学习单链…

《红楼梦》诗词大全

前言&#xff1a; 博主最近二读红楼&#xff0c;幼时只觉此书开篇便人物繁杂、莺莺燕燕似多混乱&#xff0c;开篇只看黛玉哭闹了几次&#xff0c;便弃书不读&#xff0c;只觉困惑&#xff0c;其何敢称六大奇书或四大名著&#xff1f; 今日书荒&#xff0c;偶然间再次拾起红楼…