《精通react/vue组件设计》之快速实现一个可定制的进度条组件

前言

这篇文章是笔者写组件设计的第四篇文章,之所以会写组件设计相关的文章,是因为作为一名前端优秀的前端工程师,面对各种繁琐而重复的工作,我们不应该按部就班的去"辛勤劳动",而是要根据已有前端的开发经验,总结出一套自己的高效开发的方法.作为数据驱动的领导者react/vue等MVVM框架的出现,帮我们减少了工作中大量的冗余代码, 一切皆组件的思想深得人心.所以, 为了让工程师们有更多的时间去考虑业务和产品迭代,我们不得不掌握高质量组件设计的思路和方法.所以笔者将花时间去总结各种业务场景下的组件的设计思路和方法,并用原生框架的语法去实现各种常用组件的开发,希望等让前端新手或者有一定工作经验的朋友能有所收获.

今天要来实现一个高可定制的进度条组件,在介绍组件设计之前,我们先牢记以下几个原则.

每日一学: 组件设计三原则

  • 高内聚, 低耦合(尤其是vue/react组件中, 降低组件之间的耦合尤为重要)
  • 组件边界划分清晰(每一个组件都有自己清晰的边界划分)
  • 单一职责(每一个组件只负责某一特定的表现或者功能)

如果对于react/vue组件设计原理不熟悉的,可以参考我的上一篇文章:

《精通react/vue组件设计》之用纯css打造类materialUI的按钮点击动画并封装成react组件

正文

在开始组件设计之前希望大家对css3和js有一定的基础.我们先看看实现后的组件效果:

上图可以知道封装后的进度条组件通过对外暴露的接口(react/vue里面可以看做props属性)可以很快的实现多个不同的表现和重用.我将会使用react带大家实现这个进度条组件, 大家不用担心技术栈不一样,因为react实现的组件可以很快套用于vue项目中, 所以说底层原理非常重要.

1. 组件原理和设计思路

由于组件设计的前提还是基于需求, 所以我们第一步是要确认需求. 一个进度条组件一般都会有如下需求点:

  • 通过进度控制进度条长度
  • 进度条总长度可以由用户来控制
  • 随时修改精度条的额颜色(来自于设计师或产品经理独特而百变的审美)
  • 当进度为100%时进度条可以自动消失(可能的需求)
  • 进度提示文本(用户想知道当前长度下的具体进度, 比如体温计)
  • 对于不同的进度节点,需要有不同的进度条颜色(比如游戏人物里的血, 快没血的时候为红色, 血满的时候为蓝色)

需求收集好之后,作为一个有追求的程序员, 会得出如下线框图: 这也是一个健壮的react/vue组件应有的思考角度.对于react选手来说,如果没用typescript,我建议大家都用PropTypes, 它是react内置的类型检测工具,我们可以直接在项目中导入. vue有自带的属性检测方式,笔者在这一点上认为vue还是很贴心的.

上面的思维导图我们也知道了, 进度条组件的实现原理就是通过对外暴露一定的属性,使用css先画一个进度条, 最后通过属性和样式之间的调度来实现我们需求满满的进度条.至于如何画进度条,下面会详细介绍.

2. 基于react实现一个可定制的进度条组件

2.1. 实现进度条的静态样式

首先我们会有一个容器来包裹我们的进度条,进度条和进度提示文字分开(为了更灵活的配置), 这样我们会得到一个如下的html结构:

1<div className={styles.progressWrap}> 2 <div className={styles.progressBar}> 3 <div className={styles.progressInnerBar}></div> 4 </div> 5 <span className={styles.progressText}>{percent + '%'}</span> 6 } 7</div>

.progressBar用来做进度条背景, .progressInnerBar用来做实际的进度条, .progressText为进度条文本.我们通过控制.progressInnerBar的宽度就能实现进度条的变化了, css代码如下:

1.progressWrap { 2 margin: 6px 3px; 3 display: inline-flex; 4 align-items: center; 5 .progressBar { 6 position: relative; 7 display: inline-block; 8 height: 10px; 9 background-color: #f0f0f0; 10 border-radius: 5px; 11 overflow: hidden; 12 .progressInnerBar { 13 position: absolute; 14 height: 100%; 15 } 16 } 17 .progressText { 18 margin-left: 6px; 19 margin-top: -2px; 20 font-size: 14px; 21 } 22}

没错,css代码就这么简单, 我们用了css3比较流行的额弹性布局flex, css部分由于都比较简单,这里我只提一点就是.progressInnerBar的css,使用绝对定位, 因为这个部分未来可能会做动画,所以我们把它做成离屏dom, 因为它只做展示,它的宽度完全由js控制,后面我们会将会看到.

2.2 实现组件外壳

我们根据我们收集到的需求, 可以对外暴露7个自定义属性(props),所以我们的react组件一定是这样的:

1/** 2 * 进度条组件 3 * @param {themeColor} string 进度条的颜色 4 * @param {percent} number 进度值百分比 5 * @param {autoHidden} boolean 是否进度到100%时自动消失 6 * @param {hiddenText} boolean 是否影藏进度条文本 7 * @param {width} string|number 进度条的宽度 8 * @param {textColor} string 进度文本颜色 9 * @param {statusScope} array 状态阈值,分别设置不同进度范围的进度条颜色,最大允许设置3个值, 为一个二维数组 10 */ 11function Progress(props) { 12 let { 13 themeColor = '#06f', 14 percent = 0, 15 autoHidden = false, 16 hiddenText = false, 17 width = 320, 18 textColor = '#666', 19 statusScope 20 } = props 21 return 22 <div className={styles.progressWrap}> 23 <div className={styles.progressBar} style={{ width: typeof width === 'number' ? width + 'px' : width }}> 24 <div 25 className={styles.progressInnerBar} 26 style={{ 27 width: `${percent}%` 28 }} 29 > 30 </div> 31 </div> 32 { 33 !hiddenText && <span className={styles.progressText} style={{ color: textColor }}>{percent + '%'}</span> 34 } 35 </div> 36}

根据我们收集到的额需求我们很快可以知道react组件需要暴露哪些属性,而不会造成多余的属性,这一点是非常好的设计方法, 核心思想就是基于需求设计.所以我们当确定需求之后,其实组件已经实现了.这一经验一致应用于笔者很多实际项目中,也清晰的指引着我组件的最终实现.剩几个关键点如下:

  • 设置进度区间
  • 进度为100%时进度条自动消失

3. react组件细节和最终实现

react组件中,一个属性不一定要显性的赋值才能正常工作,比如上面代码中的hiddenText属性, 如果我们不设置false或者true, 那么react会默认为false, 如果只写了hiddenText属性而不赋值, react会自动认为它的值为true.这是react的一个设计细节,希望大家能了解掌握. 设置进度区间这个需求是组件唯一比较复杂的地方(相对来说,实际项目中有更复杂的案例),对应的属性为statusScope, 它的值为一个数组,之所以为数组是为了开发人员更容易理解和使用,它的值可能如下:

1let scope = [[30, 'red'], [60, 'orange'], [80, 'blue']]

最大阈值为3,意思就是用户可以设置4种不同的进度状态.每一个状态用不同的颜色代替.由于用户可以不是按照从小到大的顺序写数组的,所以为了组件的可靠性和容错性, 笔者专门写了排序方法对用户传来的额二维数组进行排序.具体代码逻辑如下:

1// 升序排序 2let sortArr = arr => arr.sort((a,b) => a[0] - b[0]) 3 4// 检测值所对应的进度条颜色状态 5function checkStatus(scope, val, defaultColor) { 6 val = +val 7 // 从小到大排序 8 sortArr(scope) 9 10 if(scope.length === 1) { 11 return val < scope[0][0] ? scope[0][1] : defaultColor 12 }else if(scope.length === 2) { 13 return val < scope[0][0] ? scope[0][1] 14 : scope[0][0] < val && val < scope[1][0] ? scope[1][1] 15 : defaultColor 16 }else if(scope.length === 3) { 17 return val < scope[0][0] ? scope[0][1] 18 : scope[0][0] < val && val < scope[1][0] ? scope[1][1] 19 : scope[1][0] < val && val < scope[2][0] ? scope[2][1] 20 : defaultColor 21 } 22}

笔者不认为checkStatus是最优的计算阈值颜色的方法, 大家可以用更优雅的方法实现它.该方法的作用就是通过传入用户配置的区间和当前的进度值,来得到当前进度条的颜色.

进度为100%时进度条自动消失的逻辑也很简单,就是判断有这个属性,并且进度为100时将组件卸载就好了,所以相对完整的代码如下:

1import styles from './index.less' 2 3// 升序排序 4let sortArr = arr => arr.sort((a,b) => a[0] - b[0]) 5 6// 检测值所对应的进度条颜色状态 7function checkStatus(scope, val, defaultColor) { 8 val = +val 9 // 从小到大排序 10 sortArr(scope) 11 12 if(scope.length === 1) { 13 return val < scope[0][0] ? scope[0][1] : defaultColor 14 }else if(scope.length === 2) { 15 return val < scope[0][0] ? scope[0][1] 16 : scope[0][0] < val && val < scope[1][0] ? scope[1][1] 17 : defaultColor 18 }else if(scope.length === 3) { 19 return val < scope[0][0] ? scope[0][1] 20 : scope[0][0] < val && val < scope[1][0] ? scope[1][1] 21 : scope[1][0] < val && val < scope[2][0] ? scope[2][1] 22 : defaultColor 23 } 24} 25 26/** 27 * 进度条组件 28 * @param {themeColor} string 进度条的颜色 29 * @param {percent} number 进度值百分比 30 * @param {autoHidden} boolean 是否进度到100%时自动消失 31 * @param {hiddenText} boolean 是否影藏进度条文本 32 * @param {width} string|number 进度条的宽度 33 * @param {textColor} string 进度文本颜色 34 * @param {statusScope} array 状态阈值,分别设置不同进度范围的进度条颜色,最大允许设置3个值, 为一个二维数组 35 */ 36function Progress(props) { 37 let { 38 themeColor = '#06f', 39 percent = 0, 40 autoHidden = false, 41 hiddenText = false, 42 width = 320, 43 textColor = '#666', 44 statusScope 45 } = props 46 return +percent === 100 && autoHidden ? 47 null : 48 <div className={styles.progressWrap}> 49 <div className={styles.progressBar} style={{ width: typeof width === 'number' ? width + 'px' : width }}> 50 <div 51 className={styles.progressInnerBar} 52 style={{ 53 width: `${percent}%`, 54 backgroundColor: statusScope && statusScope.length ? checkStatus(statusScope, percent, themeColor) : themeColor 55 }} 56 > 57 </div> 58 </div> 59 { 60 !hiddenText && <span className={styles.progressText} style={{ color: textColor }}>{percent + '%'}</span> 61 } 62 </div> 63}

大家也许觉得到这里我们的组件就做好了.其实为了我们组件能够健壮的执行,我们用propType来对属性进行检测.关于react的propTypes的用法,我们可以去react官网自行学习,用法也很简单, 一下代码我也会做完善的额注释. 下面看看我们完整的效果演示:

完整代码如下:

1import PropTypes from 'prop-types' 2import styles from './index.less' 3 4// 升序排序 5let sortArr = arr => arr.sort((a,b) => a[0] - b[0]) 6 7// 检测值所对应的进度条颜色状态 8function checkStatus(scope, val, defaultColor) { 9 val = +val 10 // 从小到大排序 11 sortArr(scope) 12 13 if(scope.length === 1) { 14 return val < scope[0][0] ? scope[0][1] : defaultColor 15 }else if(scope.length === 2) { 16 return val < scope[0][0] ? scope[0][1] 17 : scope[0][0] < val && val < scope[1][0] ? scope[1][1] 18 : defaultColor 19 }else if(scope.length === 3) { 20 return val < scope[0][0] ? scope[0][1] 21 : scope[0][0] < val && val < scope[1][0] ? scope[1][1] 22 : scope[1][0] < val && val < scope[2][0] ? scope[2][1] 23 : defaultColor 24 } 25} 26 27 28/** 29 * 进度条组件 30 * @param {themeColor} string 进度条的颜色 31 * @param {percent} number 进度值百分比 32 * @param {autoHidden} boolean 是否进度到100%时自动消失 33 * @param {hiddenText} boolean 是否影藏进度条文本 34 * @param {width} string|number 进度条的宽度 35 * @param {textColor} string 进度文本颜色 36 * @param {statusScope} array 状态阈值,分别设置不同进度范围的进度条颜色,最大允许设置3个值, 为一个二维数组 37 */ 38function Progress(props) { 39 let { 40 themeColor = '#06f', 41 percent = 0, 42 autoHidden = false, 43 hiddenText = false, 44 width = 320, 45 textColor = '#666', 46 statusScope 47 } = props 48 return +percent === 100 && autoHidden ? 49 null : 50 <div className={styles.progressWrap}> 51 <div className={styles.progressBar} style={{ width: typeof width === 'number' ? width + 'px' : width }}> 52 <div 53 className={styles.progressInnerBar} 54 style={{ 55 width: `${percent}%`, 56 backgroundColor: statusScope && statusScope.length ? checkStatus(statusScope, percent, themeColor) : themeColor 57 }} 58 > 59 </div> 60 </div> 61 { 62 !hiddenText && <span className={styles.progressText} style={{ color: textColor }}>{percent + '%'}</span> 63 } 64 </div> 65} 66 67Progress.propTypes = { 68 themeColor: PropTypes.string, 69 percent: PropTypes.number, 70 autoHidden: PropTypes.bool, 71 textAlign: PropTypes.string, 72 hiddenText: PropTypes.bool, 73 width: PropTypes.oneOfType([ 74 PropTypes.string, 75 PropTypes.number 76 ]), 77 statusScope: PropTypes.array 78} 79 80export default Progress

关于如何使用,我就不做过多说明了,这里举2个例子:

1<Progress 2 percent={percent} 3 width={240} 4 autoHidden 5/> 6 7<Progress 8 percent={10} 9 themeColor="#6699FF" 10 statusScope={[[18, 'red'], [40, 'orange']]} 11/>

最后

如果想学习更多H5游戏, webpacknodegulpcss3javascriptnodeJScanvas数据可视化等前端知识和实战,欢迎在公号《趣谈前端》加入我们一起学习讨论,共同探索前端的边界。

更多推荐

点赞
收藏

评论区

加载中...

相关推荐

MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1

文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s

Oracle 分组与拼接字符串同时使用

SELECTT.,ROWNUMIDFROM(SELECTT.EMPLID,T.NAME,T.BU,T.REALDEPART,T.FORMATDATE,SUM(T.S0)S0,MAX(UPDATETIME)CREATETIME,LISTAGG(TOCHAR(

MySQL部分从库上面因为大量的临时表tmp_table造成慢查询

背景描述Time:20190124T00:08:14.70572408:00User@Host:@Id:Schema:sentrymetaLast_errno:0Killed:0Query_time:0.315758Lock_

皕杰报表之UUID

​在我们用皕杰报表工具设计填报报表时,如何在新增行里自动增加id呢?能新增整数排序id吗?目前可以在新增行里自动增加id,但只能用uuid函数增加UUID编码,不能新增整数排序id。uuid函数说明:获取一个UUID,可以在填报表中用来创建数据ID语法:uuid()或uuid(sep)参数说明:sep布尔值,生成的uuid中是否包含分隔符'',缺省为

手写Java HashMap源码

HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程22

java将前端的json数组字符串转换为列表

记录下在前端通过ajax提交了一个json数组的字符串,在后端如何转换为列表。前端数据转化与请求varcontracts{id:'1',name:'yanggb合同1'},{id:'2',name:'yanggb合同2'},{id:'3',name:'yang