本文轉載自【微信公眾號:趣談前端,ID:beautifulFront】經微信公眾號授權轉載,如需轉載與原文作者聯繫
前言
這篇文章是筆者寫組件設計的第四篇文章,之所以會寫組件設計相關的文章,是因為作為一名前端優秀的前端工程師,面對各種繁瑣而重複的工作,我們不應該按部就班的去"辛勤勞動",而是要根據已有前端的開發經驗,總結出一套自己的高效開發的方法.作為數據驅動的領導者react/vue等MVVM框架的出現,幫我們減少了工作中大量的冗餘代碼, 一切皆組件的思想深得人心.所以, 為了讓工程師們有更多的時間去考慮業務和產品迭代,我們不得不掌握高質量組件設計的思路和方法.所以筆者將花時間去總結各種業務場景下的組件的設計思路和方法,並用原生框架的語法去實現各種常用組件的開發,希望等讓前端新手或者有一定工作經驗的朋友能有所收穫.
今天要來實現一個高可定製的進度條組件,在介紹組件設計之前,我們先牢記以下幾個原則.
每日一學: 組件設計三原則
高內聚, 低耦合(尤其是vue/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']]}
/>