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

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


前言


这篇文章是笔者写组件设计的第四篇文章,之所以会写组件设计相关的文章,是因为作为一名前端优秀的前端工程师,面对各种繁琐而重复的工作,我们不应该按部就班的去"辛勤劳动",而是要根据已有前端的开发经验,总结出一套自己的高效开发的方法.作为数据驱动的领导者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结构:


<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控制,后面我们会将会看到.


2.2 实现组件外壳


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


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


3. 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']]}
/>


目录
相关文章
|
28天前
|
缓存 JavaScript UED
Vue3中v-model在处理自定义组件双向数据绑定时有哪些注意事项?
在使用`v-model`处理自定义组件双向数据绑定时,要仔细考虑各种因素,确保数据的准确传递和更新,同时提供良好的用户体验和代码可维护性。通过合理的设计和注意事项的遵循,能够更好地发挥`v-model`的优势,实现高效的双向数据绑定效果。
128 64
|
1月前
|
存储 缓存 JavaScript
如何优化React或Vue应用的性能
需要注意的是,性能优化是一个持续的过程,需要根据具体的应用场景和性能问题进行针对性的优化。同时,不同的项目和团队可能有不同的优化重点和方法,要结合实际情况灵活运用这些优化策略,以达到最佳的性能效果。
110 51
|
28天前
|
前端开发 JavaScript 测试技术
Vue3中v-model在处理自定义组件双向数据绑定时,如何避免循环引用?
Web 组件化是一种有效的开发方法,可以提高项目的质量、效率和可维护性。在实际项目中,要结合项目的具体情况,合理应用 Web 组件化的理念和技术,实现项目的成功实施和交付。通过不断地探索和实践,将 Web 组件化的优势充分发挥出来,为前端开发领域的发展做出贡献。
32 8
|
28天前
|
JavaScript
在 Vue 3 中,如何使用 v-model 来处理自定义组件的双向数据绑定?
需要注意的是,在实际开发中,根据具体的业务需求和组件设计,可能需要对上述步骤进行适当的调整和优化,以确保双向数据绑定的正确性和稳定性。同时,深入理解 Vue 3 的响应式机制和组件通信原理,将有助于更好地运用 `v-model` 实现自定义组件的双向数据绑定。
|
1月前
|
监控 JavaScript 前端开发
如何在实际应用中测试和比较React和Vue的性能?
总之,通过多种方法的综合运用,可以相对客观地比较 React 和 Vue 在实际应用中的性能表现,为项目的选择和优化提供有力的依据。
36 1
|
1月前
|
JavaScript 前端开发 开发者
React和Vue有什么区别?
React 和 Vue 都有各自的优势和特点,开发者可以根据项目的需求、团队的技术背景以及个人的喜好来选择使用。无论是 React 还是 Vue,它们都在不断发展和完善,为前端开发提供了强大的支持。
84 2
|
1月前
|
JavaScript 前端开发 测试技术
React和Vue的性能对比如何?
需要注意的是,性能不仅仅取决于框架本身,还与开发者的代码质量、架构设计以及项目的优化程度等密切相关。因此,在评估性能时,应该综合考虑多个因素,而不是仅仅局限于框架之间的比较。
128 1
|
1月前
|
存储 JavaScript 开发者
Vue 组件间通信的最佳实践
本文总结了 Vue.js 中组件间通信的多种方法,包括 props、事件、Vuex 状态管理等,帮助开发者选择最适合项目需求的通信方式,提高开发效率和代码可维护性。
|
1月前
|
存储 JavaScript
Vue 组件间如何通信
Vue组件间通信是指在Vue应用中,不同组件之间传递数据和事件的方法。常用的方式有:props、自定义事件、$emit、$attrs、$refs、provide/inject、Vuex等。掌握这些方法可以实现父子组件、兄弟组件及跨级组件间的高效通信。
|
1月前
|
JavaScript 前端开发 算法
React 框架和 Vue 框架的区别是什么?
React框架和Vue框架都是目前非常流行的前端JavaScript框架,它们在很多方面存在区别