【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. 内存管理:

    • 在组件卸载时清除定时器和事件监听器,避免内存泄漏。
相关推荐
Bad_Shepherd38 分钟前
vue-element-plus-admin添加自定义svg图标,并且图标选择组件可选自定义图标
vue.js·elementui·前端框架
吃饱了得干活40 分钟前
亿级订单表分库分表设计,从0到1全流程
java·数据库·面试
止语Lab2 小时前
好的 DX 不等于少写代码——三种语言的摩擦力设计课
后端
吃饱了得干活2 小时前
别再手动解析 LLM 输出了!LangChain 四种结构化输出方案对比
后端·python·langchain
程序员天天困2 小时前
Arthas trace 命令怎么用?一行定位最慢那行代码
jvm·后端
Huiturn2 小时前
GPT 5.6 连续编码 10 小时,纯 Python 啃下 Word 二进制格式——doc2docx 实现拆解
后端
用户298698530142 小时前
Python 实现 Excel 与 Markdown 互转的实用指南
后端·python·excel
用户77283104908402 小时前
krono-job:零侵入、单二进制交付的分布式任务调度平台(开源)
后端
Conan在掘金2 小时前
鸿蒙报错速查:Function lacks ending return statement,返回路径漏 return 就炸,根因 + 真解法
后端
人间凡尔赛3 小时前
AI-Native 云原生架构:2026 年从容器编排到智能体编排的范式革命
后端·云原生·架构