回想在 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 方法
- }
- }
复制代码
总结
在这次重构过程中, 我看到了之前代码耦合太严重, 仅仅满足了凑合用的情况. 如果想要再此基础上增加功能的成本很高, 所以将内部逻辑拆分. 既方便阅读和理解, 也方便日后拓展功能. 所以重构后我们便增加了两个新功能, 希望这边文章可以帮助大家.
来源: https://juejin.im/post/5b670ccfe51d4516e91fcf32