回想在4個月前剛剛進入公司實習時,我封裝了一個應用於小程式的倒數計時元件。
連結在這裡:微信小程式之倒數計時元件
以現在的視角再去看之前的實現可以說是一坨看不下去的爛程式碼。所以也藉此機會,將之前的元件重構一番。
重構舊程式碼
在原來的元件中有一個initDuration屬性和3個方法,3個方法分別是countDown,format和runCountDown。
initDuration屬性
首先我們需要三個page屬性來幫助完成接下來的程式碼,它們的名字和內容如下:
timer: null, // 儲存setInterval的ID
flag: false, // 倒數計時是否結束的標誌
num: 0 // 過去的秒數
複製程式碼
在initDuration屬性的新的回撥方法中,我們封裝了clearTimer方法,init初始化方法,並且執行倒數計時。
initDuration: {
type: Number,
value: 0,
observer: function (newVal) {
if (this.timer) {
this.clearTimer()
}
this.init() // 重置num和flag
this.runCountDown(newVal)
}
},
複製程式碼
一定要注意,當傳入的屬性的值為預設值,例如這裡是0時,是不會觸發observer回撥的。
/**
* 初始化函式
*/
init: function () {
this.flag = false
this.num = 0
}
/**
* 清空計時器
*/
clearTimer: function () {
clearInterval(this.timer)
this.timer = null
}
複製程式碼
countDown方法
countDown方法是接受一個引數為倒數計時的秒數,返回一個倒數計時的字串。在這個方法中沒有太大改動,只是改動了一些程式碼格式。如下:
/**
* 計算倒數計時
* @param {Number} duration - 秒數時間差
* @returns {string} 倒數計時的字串
*/
countDown: function (duration) {
if (duration <= 0) {
this.setFlag(true) // 將flag屬性設為true
return '00:00:00' // 返回預設時間設定
}
let seconds = this._format(duration % 60)
let minutes = Math.floor(duration / 60)
minutes = minutes >= 60 ? this._format(minutes % 60) : this._format(minutes)
let hours = this._format(Math.floor(duration / 3600))
return `${hours}:${minutes}:${seconds}`
},
複製程式碼
format方法
format方法的作用很簡單,就是處理小於10的數字展示問題。
/**
* 格式化小於10的數字
* @param {Number} time - 小於10的數字
* @returns {string} 格式化後的字串
*/
format: function (time) {
return time >= 10 ? time : `0${time}`
},
複製程式碼
runCountDown方法
runCountDown方法中的改動比較大,在原來的程式碼中邏輯比較混亂,穿插了許多無關的程式碼,其實應該將它們封裝起來達到解耦的目的。
runCountDown: function (initDuration) {
// 第一次給倒數計時賦值 this.setData({ countDownStr })
this.setCountDownTime(this.countDown(initDuration))
// 每一秒更新一次倒數計時
this.timer = setInterval(() => {
if (this.flag == true) { // 倒數計時結束
clearInterval(this.timer)
return undefined
}
this.addNum() // this.num += 1
this.setCountDownTime(this._countDown(initDuration - this.num))
}, 1000)
},
複製程式碼
增加新功能
我們原來的倒數計時元件是缺乏一些功能的,例如傳入的時間只能是秒數,倒數計時結束後只顯示00:00:00,如果傳入的值是0的話會不進行初始化(這算是Bug了)。所以我們需要加入以下的新功能:
- 支援自定義倒數計時結束後現實的字串。
- 修復傳入值為0的Bug。
- 傳入的時間可以是秒數,也可以是UTC時間的字串。
自定義結束字串
在倒數計時元件中,展示倒數計時字串的是this.data.countDownTime屬性。所以在結束時將countDownTime屬性的值設為傳入的字串即可。 首先,封裝一個賦值方法
setEndContent: function (countDownTime) {
if (countDownTime) {
this.setData({ countDownTime })
}
}
複製程式碼
接下來為元件新增加一個屬性為endContent
。
endContent: {
type: String,
value: '00:00:00'
}
複製程式碼
接下來,在倒數計時結束的位置,呼叫我們的賦值方法,也就是runCountDown方法的計時器回撥函式中。
this.timer = setInterval(() => {
if (this.flag == true) {
clearInterval(this.timer)
this.setEndContent(this.properties.endContent) // 設定結束字串
return undefined
}
this.addNum()
this.setCountDownTime(this._countDown(initDuration - this.num))
}, 1000)
複製程式碼
這樣自定義字串就成功了,在使用元件時傳入預設值即可。
修復傳入值為0的Bug
這個問題的出現是因為當傳入屬性為預設值時,不會呼叫observer回撥函式,所以這時我們需要使用元件的attached
生命週期函式。
attached: function () {
if (this.properties.initDuration <= 0) {
// 如果傳入值為零時不會呼叫observer回撥,則直接從這裡展示倒數計時結束
this.setEndContent(this.properties.endContent)
}
}
複製程式碼
可以傳入UTC時間字串
為了簡潔起見,我們就不為元件增加新的屬性了,依然使用initDuration屬性,所以要將其type從Number改為null(小程式的這點不夠強,不能選擇多型別。)。在修改type後我們需要封裝一個將UTC時間字串解析成倒數計時秒數的方法。
parseDate: function (date) {
if (typeof date == 'string') {
// 將傳進來的時間減去現在的時間,得到的結果便和直接傳進數字值相同
return Math.floor((+new Date(date) / 1000)) - Math.floor((+new Date / 1000))
}
return date
}
複製程式碼
在observer回撥中呼叫時如下:
initDuration: {
type: null,
observer: function (newVal) {
if (this.timer) {
this._clearTimer()
}
this._init()
this._runCountDown(this.parseDate(newVal)) // 在這裡呼叫parseData方法
}
}
複製程式碼
總結
在這次重構過程中,我看到了之前程式碼耦合太嚴重,僅僅滿足了湊合用的情況。如果想要再此基礎上增加功能的成本很高,所以將內部邏輯拆分。既方便閱讀和理解,也方便日後擴充功能。所以重構後我們便增加了兩個新功能,希望這邊文章可以幫助大家。
倒數計時元件程式碼:github.com/MeloGuo/wxm…
可將'count-down'資料夾直接copy到專案目錄下使用。
歡迎喜歡、關注、star、fork,當然也歡迎pr、issue.