【vue高频面试题—场景篇】:实现一个实时更新的倒计时组件,如何确保倒计时在页面切换时能够正常暂停和恢复?

设计思路一:

  1. 使用 Vue Router 的组件钩子

    • Vue Router 提供了在路由切换时触发的钩子,如 beforeRouteLeavebeforeRouteEnter,这可以帮助我们在路由切换时控制倒计时的暂停和恢复。
    • 通过在 beforeRouteLeave 钩子中暂停倒计时,在 beforeRouteEntermounted 钩子中恢复倒计时。
  2. 路由切换时的暂停与恢复

    • 暂停倒计时 :在离开当前路由时,通过 beforeRouteLeave 钩子暂停倒计时。
    • 恢复倒计时 :在进入当前路由时,通过 beforeRouteEntermounted 钩子恢复倒计时。
  3. 保持倒计时状态

    • 利用 localStoragesessionStorage,也可以在页面切换时保存倒计时状态(例如剩余时间),然后在切回该页面时恢复。

示例

js 复制代码
<template>
  <div>
    <p>倒计时:{{ formattedTime }}</p>
  </div>
</template>

<script>
import { ref, computed, onMounted, onBeforeRouteLeave, onBeforeRouteEnter } from 'vue';

export default {
  props: {
    duration: {
      type: Number,
      required: true, // 总时长(毫秒)
    },
  },
  setup(props, { emit }) {
    const remainingTime = ref(props.duration); // 剩余时间
    const timerId = ref(null); // 定时器 ID
    const lastTimestamp = ref(null); // 上次更新时间戳

    // 格式化倒计时为 HH:mm:ss
    const formattedTime = computed(() => {
      const seconds = Math.floor((remainingTime.value / 1000) % 60);
      const minutes = Math.floor((remainingTime.value / 1000 / 60) % 60);
      const hours = Math.floor(remainingTime.value / 1000 / 60 / 60);
      return `${hours.toString().padStart(2, '0')}:${minutes
        .toString()
        .padStart(2, '0')}:${seconds.toString().padStart(2, '0')}`;
    });

    // 更新倒计时
    const updateCountdown = () => {
      if (lastTimestamp.value === null) {
        lastTimestamp.value = Date.now();
      }
      const now = Date.now();
      const elapsedTime = now - lastTimestamp.value;
      lastTimestamp.value = now;
      remainingTime.value -= elapsedTime;

      if (remainingTime.value <= 0) {
        remainingTime.value = 0;
        clearInterval(timerId.value);
        timerId.value = null;
        emit('finished'); // 倒计时完成事件
      }
    };

    // 启动倒计时
    const startCountdown = () => {
      if (!timerId.value) {
        lastTimestamp.value = Date.now();
        timerId.value = setInterval(updateCountdown, 100);
      }
    };

    // 暂停倒计时
    const pauseCountdown = () => {
      if (timerId.value) {
        clearInterval(timerId.value);
        timerId.value = null;
      }
    };

    // 路由离开时暂停倒计时
    onBeforeRouteLeave(() => {
      pauseCountdown(); // 暂停倒计时
    });

    // 路由进入时恢复倒计时
    onBeforeRouteEnter((to, from, next) => {
      next((vm) => {
        startCountdown(); // 恢复倒计时
      });
    });

    // 生命周期钩子
    onMounted(() => {
      startCountdown(); // 组件挂载时启动倒计时
    });

    return {
      formattedTime,
    };
  },
};
</script>

示例解析

  1. 倒计时更新逻辑

    • remainingTime:存储剩余时间。
    • formattedTime:用来格式化显示剩余时间,按 HH:mm:ss 格式显示。
    • updateCountdown:每 100 毫秒更新一次倒计时,并计算时间差。
  2. Vue Router 钩子

    • onBeforeRouteLeave:当路由切换离开当前页面时,暂停倒计时。
    • onBeforeRouteEnter:当进入当前页面时,恢复倒计时。由于 onBeforeRouteEnter 钩子在组件挂载前执行,因此需要通过 next 回调来启动倒计时。
  3. 生命周期钩子

    • onMounted:在组件挂载时启动倒计时。

设计思路总结

  1. Vue Router 钩子:

    • 使用 onBeforeRouteLeave 在路由切换时暂停倒计时。
    • 使用 onBeforeRouteEnter 在路由切换后恢复倒计时。
  2. 倒计时状态管理

    • 使用 setInterval 进行倒计时更新,确保在组件的生命周期内倒计时是实时更新的。
    • 在组件卸载时通过清除定时器来避免内存泄漏。
  3. 页面恢复时的倒计时同步

    • 在路由进入时恢复倒计时,确保不会丢失剩余时间。
  4. 内存管理

    • 每次路由切换时暂停和恢复倒计时,确保资源不会浪费。

设计思路二:

  1. 倒计时核心逻辑

    • 使用 setIntervalrequestAnimationFrame 来实时更新倒计时。
    • 利用 Date.now() 获取当前时间,确保倒计时在暂停后能正确恢复。
    • 页面切换时通过 visibilitychange 事件暂停倒计时,切回时恢复。
  2. 页面切换处理

    • 使用 document.hiddenvisibilitychange 事件监听页面是否可见。
    • 如果页面隐藏,则暂停倒计时。
    • 如果页面恢复,则恢复倒计时,并计算时间差。
  3. 倒计时结束处理

    • 当倒计时结束时触发事件,通知父组件。

示例

js 复制代码
<template>
  <div>
    <p>倒计时:{{ formattedTime }}</p>
  </div>
</template>

<script>
import { ref, computed, onMounted, onUnmounted } from 'vue';

export default {
  props: {
    duration: {
      type: Number,
      required: true, // 倒计时总时长(毫秒)
    },
  },
  setup(props, { emit }) {
    const remainingTime = ref(props.duration); // 剩余时间
    const timerId = ref(null); // 定时器 ID
    const lastTimestamp = ref(null); // 上次更新时间戳

    // 格式化显示倒计时
    const formattedTime = computed(() => {
      const seconds = Math.floor((remainingTime.value / 1000) % 60);
      const minutes = Math.floor((remainingTime.value / 1000 / 60) % 60);
      const hours = Math.floor(remainingTime.value / 1000 / 60 / 60);
      return `${hours.toString().padStart(2, '0')}:${minutes
        .toString()
        .padStart(2, '0')}:${seconds.toString().padStart(2, '0')}`;
    });

    // 更新倒计时
    const updateCountdown = () => {
      if (lastTimestamp.value === null) {
        lastTimestamp.value = Date.now();
      }
      const now = Date.now();
      const elapsedTime = now - lastTimestamp.value;
      lastTimestamp.value = now;
      remainingTime.value -= elapsedTime;

      if (remainingTime.value <= 0) {
        remainingTime.value = 0;
        clearInterval(timerId.value);
        timerId.value = null;
        emit('finished'); // 倒计时完成事件
      }
    };

    // 启动倒计时
    const startCountdown = () => {
      if (!timerId.value) {
        lastTimestamp.value = Date.now();
        timerId.value = setInterval(updateCountdown, 100);
      }
    };

    // 暂停倒计时
    const pauseCountdown = () => {
      if (timerId.value) {
        clearInterval(timerId.value);
        timerId.value = null;
      }
    };

    // 页面切换时暂停或恢复倒计时
    const handleVisibilityChange = () => {
      if (document.hidden) {
        pauseCountdown(); // 页面隐藏时暂停
      } else {
        startCountdown(); // 页面恢复时继续
      }
    };

    onMounted(() => {
      startCountdown(); // 初始化倒计时
      document.addEventListener('visibilitychange', handleVisibilityChange); // 监听页面切换
    });

    onUnmounted(() => {
      clearInterval(timerId.value); // 清理定时器
      document.removeEventListener('visibilitychange', handleVisibilityChange); // 移除事件监听
    });

    return {
      formattedTime,
    };
  },
};
</script>

示例解析

  1. 倒计时核心逻辑:

    • remainingTime:保存当前的剩余时间。
    • timerId:用于保存 setInterval 的定时器 ID,以便暂停时可以清除。
    • lastTimestamp:保存上次更新时间的时间戳,用于计算时间差。
    • formattedTime:计算剩余时间并格式化为 HH:mm:ss 格式。
  2. 倒计时更新:

    • updateCountdown:每 100 毫秒更新一次倒计时,计算时间差并更新剩余时间。
    • 使用 Date.now() 获取当前时间,通过时间差计算减少的时间量。
  3. 页面切换处理:

    • 使用 visibilitychange 事件检测页面是否可见。当页面不可见时,调用 pauseCountdown() 暂停倒计时;当页面恢复时,调用 startCountdown() 恢复倒计时。
  4. 生命周期管理:

    • onMounted 中启动倒计时并绑定事件监听。
    • onUnmounted 中清除定时器并移除事件监听,防止内存泄漏。

设计思路总结

  1. 倒计时精度:

    • 使用 Date.now() 来计算时间差,确保即使倒计时被暂停或恢复,剩余时间计算也不会出现误差。
  2. 页面切换处理:

    • 通过 visibilitychange 事件来处理页面可见性状态,在页面切换时暂停或恢复倒计时。
    • 使用 document.hidden 判断页面是否隐藏。
  3. 内存管理:

    • 在组件卸载时清除定时器和事件监听器,避免内存泄漏。
相关推荐
web130933203981 小时前
vue elementUI form组件动态添加el-form-item并且动态添加rules必填项校验方法
前端·vue.js·elementui
测试老哥1 小时前
外包干了两年,技术退步明显。。。。
自动化测试·软件测试·python·功能测试·测试工具·面试·职场和发展
追逐时光者2 小时前
免费、简单、直观的数据库设计工具和 SQL 生成器
后端·mysql
初晴~2 小时前
【Redis分布式锁】高并发场景下秒杀业务的实现思路(集群模式)
java·数据库·redis·分布式·后端·spring·
盖世英雄酱581362 小时前
InnoDB 的页分裂和页合并
数据库·后端
supermapsupport3 小时前
iClient3D for Cesium在Vue中快速实现场景卷帘
前端·vue.js·3d·cesium·supermap
小_太_阳3 小时前
Scala_【2】变量和数据类型
开发语言·后端·scala·intellij-idea
直裾3 小时前
scala借阅图书保存记录(三)
开发语言·后端·scala
m0_748254883 小时前
vue+elementui实现下拉表格多选+搜索+分页+回显+全选2.0
前端·vue.js·elementui