vue小记:封装倒计时组件

311 阅读2分钟

e1fd99b5f08f77da7960336121a74679.jpg

倒计时通用组件

组件功能说明:

  1. 接收 endTime 属性: 组件接收一个 endTime 属性,用于设置倒计时的到期时间。endTime 可以是 Date 对象、时间戳或格式化的时间字符串。
  2. 支持插槽自定义显示内容:
    • 默认插槽:用于显示倒计时,可以访问 dayshoursminutesseconds 数据。
    • ended 插槽:用于在倒计时结束后显示自定义内容。
  3. 倒计时结束时通知父组件: 当倒计时结束时,组件会触发 countdown-ended 事件,父组件可以监听该事件并调用 getNewEndTime 方法获取最新的到期时间。
  4. 重新开始倒计时: 父组件获取到最新的到期时间后,更新 endTime 属性,组件会自动重新开始倒计时。
  5. 清除定时器: 组件在销毁前会清除定时器,避免对页面产生负担。

代码:

<template>
  <div v-if="timeRemaining > 0">
    <slot :days="days" :hours="hours" :minutes="minutes" :seconds="seconds">
      {{ days }} 天 {{ hours }} 小时 {{ minutes }} 分钟 {{ seconds }} 秒
    </slot>
  </div>
  <div v-else>
    <slot name="ended"> 倒计时结束 </slot>
  </div>
</template>

<script>
export default {
  name: 'Countdown',
  props: {
    endTime: {
      type: [Date, String, Number],
      required: true,
    },
  },
  data() {
    return {
      timeRemaining: 0,
      days: 0,
      hours: 0,
      minutes: 0,
      seconds: 0,
      timer: null,
    };
  },
  mounted() {
    this.startCountdown();
  },
  beforeDestroy() {
    this.clearTimer();
  },
  methods: {
    startCountdown() {
      this.updateCountdown();
      
      this.timer = setInterval(this.updateCountdown, 1000);
    },
    clearTimer() {
      clearInterval(this.timer);
      this.timer = null;
    },
    updateCountdown() {
      const endTime = new Date(this.endTime).getTime();
      const now = Date.now();
      this.timeRemaining = endTime - now;

      if (this.timeRemaining <= 0) {
        this.clearTimer();
        this.$emit('countdown-ended');
        return;
      }

      this.days = Math.floor(this.timeRemaining / (1000 * 60 * 60 * 24));
      this.hours = Math.floor((this.timeRemaining % (1000 * 60 * 60 * 24)) / (1000 * 60 * 60));
      this.minutes = Math.floor((this.timeRemaining % (1000 * 60 * 60)) / (1000 * 60));
      this.seconds = Math.floor((this.timeRemaining % (1000 * 60)) / 1000);
    },
  },
};
</script>

注意:

  • getNewEndTime 方法中,需要自行实现获取最新到期时间的逻辑,例如调用后端接口。
  • 组件使用了 setInterval 函数,每秒更新一次倒计时。
  • 组件在 beforeDestroy 生命周期钩子函数中清除了定时器,确保组件销毁时不会继续占用资源。

setTimeout 函数替代 setInterval 来实现倒计时功能。

思路:

  1. updateCountdown 函数结束时,判断 timeRemaining 是否大于 0。
  2. 如果大于 0,则使用 setTimeout 递归调用 updateCountdown 函数自身,延迟时间设置为 1000 毫秒(1 秒)。
  3. 如果小于等于 0,说明倒计时结束,清除定时器,并触发 countdown-ended 事件。

代码:

<template>
  <div v-if="timeRemaining > 0">
    <slot :days="days" :hours="hours" :minutes="minutes" :seconds="seconds">
      {{ days }} 天 {{ hours }} 小时 {{ minutes }} 分钟 {{ seconds }} 秒
    </slot>
  </div>
  <div v-else>
    <slot name="ended"> 倒计时结束 </slot>
  </div>
</template>

<script>
export default {
  name: 'Countdown',
  props: {
    endTime: {
      type: [Date, String, Number],
      required: true,
    },
  },
  data() {
    return {
      timeRemaining: 0,
      days: 0,
      hours: 0,
      minutes: 0,
      seconds: 0,
      timer: null,
    };
  },
  mounted() {
    this.startCountdown();
  },
  beforeDestroy() {
    this.clearTimer();
  },
  methods: {
    startCountdown() {
      this.updateCountdown();
    },
    clearTimer() {
      clearTimeout(this.timer);
      this.timer = null;
    },
    updateCountdown() {
      const endTime = new Date(this.endTime).getTime();
      const now = Date.now();
      this.timeRemaining = endTime - now;

      if (this.timeRemaining <= 0) {
        this.clearTimer();
        this.$emit('countdown-ended');
        return;
      }

      this.days = Math.floor(this.timeRemaining / (1000 * 60 * 60 * 24));
      this.hours = Math.floor((this.timeRemaining % (1000 * 60 * 60 * 24)) / (1000 * 60 * 60));
      this.minutes = Math.floor((this.timeRemaining % (1000 * 60 * 60)) / (1000 * 60));
      this.seconds = Math.floor((this.timeRemaining % (1000 * 60)) / 1000);

      // 使用 setTimeout 递归调用 updateCountdown
      this.timer = setTimeout(this.updateCountdown, 1000);
    },
  },
};
</script>

变化:

  • 使用 setTimeout 替换了 setInterval
  • updateCountdown 函数内部,使用 setTimeout 递归调用自身,实现每秒更新倒计时。
  • clearTimer 函数中,使用 clearTimeout 清除 setTimeout 设置的定时器。

使用 setTimeout 的方式可以避免一些 setInterval 潜在的问题,例如:

  • setInterval 不会检查代码执行是否完成,如果代码执行时间超过了设置的间隔时间,可能会导致定时器堆积,影响性能。
  • setTimeout 则会在每次代码执行完成后才设置下一次定时,更加安全可靠。

组件使用方法:

<template>
  <div>
    <Countdown :endTime="endTime" @countdown-ended="getNewEndTime">
      <template #default="{ days, hours, minutes, seconds }">
        距离活动结束还有:{{ days }}天{{ hours }}小时{{ minutes }}分钟{{ seconds }}秒
      </template>
      <template #ended>
        活动已结束!
      </template>
    </Countdown>
  </div>
</template>

<script>
export default {
  data() {
    return {
      endTime: '2024-01-01', // 设置初始到期时间
    };
  },
  methods: {
    getNewEndTime() {
      // 此处调用接口获取最新的到期时间
      // 并更新 this.endTime
      // ...
    },
  },
};
</script>