这篇文章是笔者写组件设计的第四篇文章,之因此会写组件设计相关的文章,是由于做为一名前端优秀的前端工程师,面对各类繁琐而重复的工做,咱们不该该循序渐进的去"辛勤劳动",而是要根据已有前端的开发经验,总结出一套本身的高效开发的方法.做为数据驱动的领导者react/vue等MVVM框架的出现,帮咱们减小了工做中大量的冗余代码, 一切皆组件的思想深得人心.因此, 为了让工程师们有更多的时间去考虑业务和产品迭代,咱们不得不掌握高质量组件设计的思路和方法.因此笔者将花时间去总结各类业务场景下的组件的设计思路和方法,并用原生框架的语法去实现各类经常使用组件的开发,但愿等让前端新手或者有必定工做经验的朋友能有所收获.javascript
今天要来实现一个高可定制的进度条组件,在介绍组件设计以前,咱们先牢记如下几个原则.css
每日一学: 组件设计三原则html
若是对于react/vue组件设计原理不熟悉的,能够参考个人上一篇文章:前端
《精通react/vue组件设计》之用纯css打造类materialUI的按钮点击动画并封装成react组件vue
在开始组件设计以前但愿你们对css3和js有必定的基础.咱们先看看实现后的组件效果:java
因为组件设计的前提仍是基于需求, 因此咱们第一步是要确认需求. 一个进度条组件通常都会有以下需求点:node
需求收集好以后,做为一个有追求的程序员, 会得出以下线框图: react
上面的思惟导图咱们也知道了, 进度条组件的实现原理就是经过对外暴露必定的属性,使用css先画一个进度条, 最后经过属性和样式之间的调度来实现咱们需求满满的进度条.至于如何画进度条,下面会详细介绍.webpack
首先咱们会有一个容器来包裹咱们的进度条,进度条和进度提示文字分开(为了更灵活的配置), 这样咱们会获得一个以下的html结构:css3
<div className={styles.progressWrap}>
<div className={styles.progressBar}>
<div className={styles.progressInnerBar}></div>
</div>
<span className={styles.progressText}>{percent + '%'}</span>
}
</div>
复制代码
.progressBar用来作进度条背景, .progressInnerBar用来作实际的进度条, .progressText为进度条文本.咱们经过控制.progressInnerBar的宽度就能实现进度条的变化了, css代码以下:
.progressWrap {
margin: 6px 3px;
display: inline-flex;
align-items: center;
.progressBar {
position: relative;
display: inline-block;
height: 10px;
background-color: #f0f0f0;
border-radius: 5px;
overflow: hidden;
.progressInnerBar {
position: absolute;
height: 100%;
}
}
.progressText {
margin-left: 6px;
margin-top: -2px;
font-size: 14px;
}
}
复制代码
没错,css代码就这么简单, 咱们用了css3比较流行的额弹性布局flex, css部分因为都比较简单,这里我只提一点就是.progressInnerBar的css,使用绝对定位, 由于这个部分将来可能会作动画,因此咱们把它作成离屏dom, 由于它只作展现,它的宽度彻底由js控制,后面咱们会将会看到.
咱们根据咱们收集到的需求, 能够对外暴露7个自定义属性(props),因此咱们的react组件必定是这样的:
/** * 进度条组件 * @param {themeColor} string 进度条的颜色 * @param {percent} number 进度值百分比 * @param {autoHidden} boolean 是否进度到100%时自动消失 * @param {hiddenText} boolean 是否影藏进度条文本 * @param {width} string|number 进度条的宽度 * @param {textColor} string 进度文本颜色 * @param {statusScope} array 状态阈值,分别设置不一样进度范围的进度条颜色,最大容许设置3个值, 为一个二维数组 */
function Progress(props) {
let {
themeColor = '#06f',
percent = 0,
autoHidden = false,
hiddenText = false,
width = 320,
textColor = '#666',
statusScope
} = props
return
<div className={styles.progressWrap}> <div className={styles.progressBar} style={{ width: typeof width === 'number' ? width + 'px' : width }}> <div className={styles.progressInnerBar} style={{ width: `${percent}%` }} > </div> </div> { !hiddenText && <span className={styles.progressText} style={{ color: textColor }}>{percent + '%'}</span> } </div>
}
复制代码
根据咱们收集到的额需求咱们很快能够知道react组件须要暴露哪些属性,而不会形成多余的属性,这一点是很是好的设计方法, 核心思想就是基于需求设计.因此咱们当肯定需求以后,其实组件已经实现了.这一经验一致应用于笔者不少实际项目中,也清晰的指引着我组件的最终实现.剩几个关键点以下:
react组件中,一个属性不必定要显性的赋值才能正常工做,好比上面代码中的hiddenText属性, 若是咱们不设置false或者true, 那么react会默认为false, 若是只写了hiddenText属性而不赋值, react会自动认为它的值为true.这是react的一个设计细节,但愿你们能了解掌握. 设置进度区间这个需求是组件惟一比较复杂的地方(相对来讲,实际项目中有更复杂的案例),对应的属性为statusScope, 它的值为一个数组,之因此为数组是为了开发人员更容易理解和使用,它的值可能以下:
let scope = [[30, 'red'], [60, 'orange'], [80, 'blue']]
复制代码
最大阈值为3,意思就是用户能够设置4种不一样的进度状态.每个状态用不一样的颜色代替.因为用户能够不是按照从小到大的顺序写数组的,因此为了组件的可靠性和容错性, 笔者专门写了排序方法对用户传来的额二维数组进行排序.具体代码逻辑以下:
// 升序排序
let sortArr = arr => arr.sort((a,b) => a[0] - b[0])
// 检测值所对应的进度条颜色状态
function checkStatus(scope, val, defaultColor) {
val = +val
// 从小到大排序
sortArr(scope)
if(scope.length === 1) {
return val < scope[0][0] ? scope[0][1] : defaultColor
}else if(scope.length === 2) {
return val < scope[0][0] ? scope[0][1]
: scope[0][0] < val && val < scope[1][0] ? scope[1][1]
: defaultColor
}else if(scope.length === 3) {
return val < scope[0][0] ? scope[0][1]
: scope[0][0] < val && val < scope[1][0] ? scope[1][1]
: scope[1][0] < val && val < scope[2][0] ? scope[2][1]
: defaultColor
}
}
复制代码
笔者不认为checkStatus是最优的计算阈值颜色的方法, 你们能够用更优雅的方法实现它.该方法的做用就是经过传入用户配置的区间和当前的进度值,来获得当前进度条的颜色.
进度为100%时进度条自动消失的逻辑也很简单,就是判断有这个属性,而且进度为100时将组件卸载就行了,因此相对完整的代码以下:
import styles from './index.less'
// 升序排序
let sortArr = arr => arr.sort((a,b) => a[0] - b[0])
// 检测值所对应的进度条颜色状态
function checkStatus(scope, val, defaultColor) {
val = +val
// 从小到大排序
sortArr(scope)
if(scope.length === 1) {
return val < scope[0][0] ? scope[0][1] : defaultColor
}else if(scope.length === 2) {
return val < scope[0][0] ? scope[0][1]
: scope[0][0] < val && val < scope[1][0] ? scope[1][1]
: defaultColor
}else if(scope.length === 3) {
return val < scope[0][0] ? scope[0][1]
: scope[0][0] < val && val < scope[1][0] ? scope[1][1]
: scope[1][0] < val && val < scope[2][0] ? scope[2][1]
: defaultColor
}
}
/** * 进度条组件 * @param {themeColor} string 进度条的颜色 * @param {percent} number 进度值百分比 * @param {autoHidden} boolean 是否进度到100%时自动消失 * @param {hiddenText} boolean 是否影藏进度条文本 * @param {width} string|number 进度条的宽度 * @param {textColor} string 进度文本颜色 * @param {statusScope} array 状态阈值,分别设置不一样进度范围的进度条颜色,最大容许设置3个值, 为一个二维数组 */
function Progress(props) {
let {
themeColor = '#06f',
percent = 0,
autoHidden = false,
hiddenText = false,
width = 320,
textColor = '#666',
statusScope
} = props
return +percent === 100 && autoHidden ?
null :
<div className={styles.progressWrap}> <div className={styles.progressBar} style={{ width: typeof width === 'number' ? width + 'px' : width }}> <div className={styles.progressInnerBar} style={{ width: `${percent}%`, backgroundColor: statusScope && statusScope.length ? checkStatus(statusScope, percent, themeColor) : themeColor }} > </div> </div> { !hiddenText && <span className={styles.progressText} style={{ color: textColor }}>{percent + '%'}</span> } </div>
}
复制代码
你们也许以为到这里咱们的组件就作好了.其实为了咱们组件可以健壮的执行,咱们用propType来对属性进行检测.关于react的propTypes的用法,咱们能够去react官网自行学习,用法也很简单, 一下代码我也会作完善的额注释. 下面看看咱们完整的效果演示:
完整代码以下:
import PropTypes from 'prop-types'
import styles from './index.less'
// 升序排序
let sortArr = arr => arr.sort((a,b) => a[0] - b[0])
// 检测值所对应的进度条颜色状态
function checkStatus(scope, val, defaultColor) {
val = +val
// 从小到大排序
sortArr(scope)
if(scope.length === 1) {
return val < scope[0][0] ? scope[0][1] : defaultColor
}else if(scope.length === 2) {
return val < scope[0][0] ? scope[0][1]
: scope[0][0] < val && val < scope[1][0] ? scope[1][1]
: defaultColor
}else if(scope.length === 3) {
return val < scope[0][0] ? scope[0][1]
: scope[0][0] < val && val < scope[1][0] ? scope[1][1]
: scope[1][0] < val && val < scope[2][0] ? scope[2][1]
: defaultColor
}
}
/** * 进度条组件 * @param {themeColor} string 进度条的颜色 * @param {percent} number 进度值百分比 * @param {autoHidden} boolean 是否进度到100%时自动消失 * @param {hiddenText} boolean 是否影藏进度条文本 * @param {width} string|number 进度条的宽度 * @param {textColor} string 进度文本颜色 * @param {statusScope} array 状态阈值,分别设置不一样进度范围的进度条颜色,最大容许设置3个值, 为一个二维数组 */
function Progress(props) {
let {
themeColor = '#06f',
percent = 0,
autoHidden = false,
hiddenText = false,
width = 320,
textColor = '#666',
statusScope
} = props
return +percent === 100 && autoHidden ?
null :
<div className={styles.progressWrap}> <div className={styles.progressBar} style={{ width: typeof width === 'number' ? width + 'px' : width }}> <div className={styles.progressInnerBar} style={{ width: `${percent}%`, backgroundColor: statusScope && statusScope.length ? checkStatus(statusScope, percent, themeColor) : themeColor }} > </div> </div> { !hiddenText && <span className={styles.progressText} style={{ color: textColor }}>{percent + '%'}</span> } </div>
}
Progress.propTypes = {
themeColor: PropTypes.string,
percent: PropTypes.number,
autoHidden: PropTypes.bool,
textAlign: PropTypes.string,
hiddenText: PropTypes.bool,
width: PropTypes.oneOfType([
PropTypes.string,
PropTypes.number
]),
statusScope: PropTypes.array
}
export default Progress
复制代码
关于如何使用,我就不作过多说明了,这里举2个例子:
<Progress percent={percent} width={240} autoHidden />
<Progress percent={10} themeColor="#6699FF" statusScope={[[18, 'red'], [40, 'orange']]} />
复制代码
若是想学习更多H5游戏, webpack,node,gulp,css3,javascript,nodeJS,canvas数据可视化等前端知识和实战,欢迎在公号《趣谈前端》加入咱们一块儿学习讨论,共同探索前端的边界。