Vue 封装一个倒计时组件

1. 创建一个基础组件

<template>
  <div class="_base-count-down"></div>
</template>

<script>
export default {
  name: 'index',
  props: {},
  data () {
    return {}
  }
}
</script>

<style lang="scss" scoped>

</style>

2. 实现基本的倒计时组件

  • time时间转化
    • time:剩余时间
    • isMilliSecond:告诉组件time是毫秒还是秒为单位
    • duration:将time进行转化的结果
...

<script>
export default {
  name: 'index',
  props: {
    // 新增代码
    time: {
      type: [String, Number],
      default: 0
    },
    // 新增代码
    isMilliSecond: {
      type: Boolean,
      default: false
    }
  },
  data () {
    return {}
  },
  computed: {
    // 新增代码
    duration () {
      return this.isMilliSecond ? Math.round(+this.time / 1000) : Math.round(+this.time)
    }
  }
}
</script>

...
  • 开始倒计时
    • countDown:开始倒计时的方法(进入页面就开始执行)
...

<script>
export default {
  ...
  // 新增代码
  mounted () {
    this.countDown()
  },
  // 新增代码
  methods: {
    countDown () {
      this.getTime(this.duration)
    }
  }
}
</script>

...
  • 时间转化天数、小时、分钟、秒数
    • getTime:获得days, hours, mins, seconds,然后显示
    • durationFormatter:将时间转化成天数,小时,分钟,秒数的方法
<template>
  <div class="_base-count-down">
    <!-- 新增代码 -->
    还剩{{days}}{{hours}}:{{mins}}:{{seconds}}
  </div>
</template>

<script>
export default {
  ...
  props: {...},
  // 新增代码
  data () {
    return {
      days: '0',
      hours: '00',
      mins: '00',
      seconds: '00',
      timer: null
    }
  },
  computed: {...},
  mounted () {...},
  methods: {
    countDown () {
      this.getTime(this.duration)
    },
    getTime (duration) {
      // 执行时,将上一个setTimeout清除掉
      this.timer && clearTimeout(this.timer)
      if (duration < 0) return

      const { dd, hh, mm, ss } = this.durationFormatter(duration)
      this.days = dd || 0
      this.hours = hh || 0
      this.mins = mm || 0
      this.seconds = ss || 0
      this.timer = setTimeout(() => {
        this.getTime(duration - 1)
      }, 1000)
    },
    durationFormatter (time) {
      if (!time) return { ss: 0 }
      let t = time

      const ss = t % 60
      t = (t - ss) / 60
      if (t < 1) return { ss }

      const mm = t % 60
      t = (t - mm) / 60
      if (t < 1) return { mm, ss }

      const hh = t % 24
      t = (t - hh) / 24
      if (t < 1) return { hh, mm, ss }

      const dd = t
      return { dd, hh, mm, ss }
    }
  }
}
</script>

...

3. setTimeout和setInterval使用比较

setTimeout(function(){···}, n); // n毫秒后执行function
setInterval(function(){···}, n); // 每隔n毫秒执行一次function

这里使用setInterval不是更加方便吗?

强调:定时器指的是时间间隔,表示的是何时将定时器的代码添加到消息队列,而不是何时执行代码。倒置何时执行代码的时间是不能保证的,取决于何时被主线程的事件循环取到,再执行。

setInterval(function, N)
// 即:每隔N秒吧function事件推到消息队列中,并不是隔N秒后执行代码
  • setInterval每隔N秒,会往消息队列中添加一个事件;
  • N秒后,添加a定时器代码至队列中,若主线程中还有其他任务在执行,所以需要等待;
  • 当其他任务执行结束后执行a定时器代码 ;
  • 又过了N秒,b定时器被添加到队列中,主线程还在执行a代码,所以等待;
  • 又过了N秒,理论上又要往队列里推一个定时器代码,但由于此时b还在队列中,所以c不会被添加,结果就是此时c被跳过;
  • 可以看到,a定时器执行结束后马上执行了b代码,所以并没有定时器的效果。

综上所述,setInterval会有两个问题:

  1. 使用setInterval时,某些间隔会被跳过;
  2. 可能多个定时器会连续执行;

可以这样理解:每个setTimeout产生的任务会直接push到任务队列中,而setInterval在每次把任务push到任务队列前,都要进行一下判断(看上一次的任务是否仍在队列中)。

因而一般用setTimeout模拟setInterval,来规避上面的问题。

4. 监听时间

倒计时组件描述

如图,在倒计时的父组件中,有两个按钮,活动一和活动二,点击传入相应的时间。

若此时的倒计时组件正在做活动一的倒计时,再点击活动二,会马上传入新的time,此时应该需要重新计时。

但组件的mounted只会执行一次,则this.getTime(this.duration),只会执行一次,因此duration还是活动一的时间。

通过watch监听duration,当发现变化时,说明新的时间time传入组件,然后重新调用this.countDown()

...

<script>
export default {
  name: 'index',
  props: {...},
  data () {...},
  computed: {...},
  // 新增代码
  watch: {
    duration () {
      this.countDown()
    }
  },
  mounted () {...},
  methods: {...}
}
</script>

...

5. 使用diffTime

注意:此时的组件只是一个基础的组件,若到了线上会有一个bug。当页面打开时,倒计时开始,时间是还剩1天12:25:25,切换应用后,一段时间再切回浏览器,发现倒计时还是还剩1天12:25:25

出于性能的考虑,部分浏览器再进入后台或失去焦点时,会将setTimeout等定时任务暂停(或者说是延迟),待用户切回时,才能重新激活定时任务。

需要改写一下getTime方法。

diffTime:表示当前这个setTimeout的回调函数执行的时刻距离上一个setTimeout的回调函数执行的时刻时间段。

...

<script>
export default {
  name: 'index',
  props: {...},
  data () {
    return {
      days: '0',
      hours: '00',
      mins: '00',
      seconds: '00',
      timer: null,
      // 新增代码
      curTime: 0
    }
  },
  computed: {...},
  watch: {...},
  mounted () {...},
  methods: {
    countDown () {
      // 新增代码
      this.curTime = Date.now()
      this.getTime(this.duration)
    },
    getTime (duration) {
      this.timer && clearTimeout(this.timer)
      if (duration < 0) return

      const { dd, hh, mm, ss } = this.durationFormatter(duration)
      this.days = dd || 0
      this.hours = hh || 0
      this.mins = mm || 0
      this.seconds = ss || 0
      this.timer = setTimeout(() => {
		// 新增代码
        const now = Date.now()
        const diffTime = Math.floor((now - this.curTime) / 1000)
        this.curTime = now
        this.getTime(duration - diffTime)
      }, 1000)
    },
    durationFormatter (time) {...}
  }
}
</script>

...

6. 添加功能

  • 传入到期时间

改动一下duration就好

...

<script>
export default {
  name: 'index',
  props: {
    ...
    // 新增代码
    end: {
      type: [String, Number],
      default: 0
    }
  },
  data () {...},
  computed: {
    duration () {
      // 新增代码
      if (this.end) {
        let end = String(this.end).length >= 13 ? +this.end : +this.end * 1000
        end -= Date.now()
        return end
      }
      return this.isMilliSecond ? Math.round(+this.time / 1000) : Math.round(+this.time)
    }
  },
  watch: {...},
  mounted () {...},
  methods: {...}
}
</script>

...
  • 选择显示内容,例如只显示秒或小时

只需改动html

<template>
  <div class="_base-count-down">
    <div class="content">
      <slot v-bind="{
        d: days, h: hours, m: mins, s:seconds,
        hh: `00${hours}`.slice(-2),
        mm: `00${mins}`.slice(-2),
        ss: `00${seconds}`.slice(-2)
      }"></slot>
    </div>
  </div>
</template>

父组件使用

<template>
  <div class="subunit">
    <base-counter :time="countDownTime" v-slot="timeObj" >
      <div class="count-down">
        <div class="icon"></div>
        {{timeObj.d}}{{timeObj.hh}}小时{{timeObj.mm}}分钟{{timeObj.ss}}</div>
    </base-counter>
  </div>
</template>

<script>
import BaseCounter from './components/CountDown.vue'

export default {
  components: { BaseCounter },
  name: 'index',
  data: () => ({
    countDownTime: 2 * 24 * 60 * 60
  })
}
</script>

<style lang="scss" scoped>

</style>

7. 完整代码

<template>
  <div class="_base-count-down">
    <div class="content">
      <slot v-bind="{
        d: days, h: hours, m: mins, s:seconds,
        hh: `00${hours}`.slice(-2),
        mm: `00${mins}`.slice(-2),
        ss: `00${seconds}`.slice(-2)
      }"></slot>
    </div>
  </div>
</template>

<script>
export default {
  name: 'index',
  props: {
    time: {
      type: [String, Number],
      default: 0
    },
    isMilliSecond: {
      type: Boolean,
      default: false
    },
    end: {
      type: [String, Number],
      default: 0
    }
  },
  data () {
    return {
      days: '0',
      hours: '00',
      mins: '00',
      seconds: '00',
      timer: null,
      curTime: 0
    }
  },
  computed: {
    duration () {
      if (this.end) {
        let end = String(this.end).length >= 13 ? +this.end : +this.end * 1000
        end -= Date.now()
        return end
      }
      return this.isMilliSecond ? Math.round(+this.time / 1000) : Math.round(+this.time)
    }
  },
  watch: {
    duration () {
      this.countDown()
    }
  },
  mounted () {
    this.countDown()
  },
  methods: {
    countDown () {
      this.curTime = Date.now()
      this.getTime(this.duration)
    },
    getTime (duration) {
      this.timer && clearTimeout(this.timer)
      if (duration < 0) return

      const { dd, hh, mm, ss } = this.durationFormatter(duration)
      this.days = dd || 0
      this.hours = hh || 0
      this.mins = mm || 0
      this.seconds = ss || 0
      this.timer = setTimeout(() => {
        const now = Date.now()
        const diffTime = Math.floor((now - this.curTime) / 1000)
        this.curTime = now
        this.getTime(duration - diffTime)
      }, 1000)
    },
    durationFormatter (time) {
      if (!time) return { ss: 0 }
      let t = time

      const ss = t % 60
      t = (t - ss) / 60
      if (t < 1) return { ss }

      const mm = t % 60
      t = (t - mm) / 60
      if (t < 1) return { mm, ss }

      const hh = t % 24
      t = (t - hh) / 24
      if (t < 1) return { hh, mm, ss }

      const dd = t
      return { dd, hh, mm, ss }
    }
  }
}
</script>

<style lang="scss" scoped>

</style>

  • 4
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值