【重構】微信小程式倒數計時元件

MeloGuo發表於2018-08-05

回想在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.

相關文章