useTimer 封装响应式计时器,以 ref 管理状态与定时器 ID,支持启动、暂停、重置、倒计时、自定义步长及运行/完成状态监听,并在卸载时自动清理,规避闭包陷阱与内存泄漏。

用响应式 ref 封装计时器 Hooks,核心是把 ref 作为状态容器 + 手动控制定时器生命周期,避免闭包陷阱和内存泄漏。关键不在“多酷”,而在“可中断、可重置、不重复启动、响应式更新”。
封装一个基础响应式计时器(useTimer)
使用 ref 存储定时器 ID 和当前秒数,配合 onUnmounted 清理,确保组件卸载时自动停止:
- 定义
time = ref(0)和timer = ref(null),两者都需响应式暴露(返回对象解构时用toRefs或直接返回 ref) - 提供
start():先清除已有定时器,再用setInterval每秒递增time.value - 提供
pause():清除定时器但保留当前值 - 提供
reset():清空定时器并重置time.value = 0 - 在
onUnmounted中调用pause(),防止组件销毁后继续运行
支持自定义步长与倒计时(增强实用性)
基础版只支持正向秒级计时,实际项目常需毫秒精度、倒计时、或按指定步长(如每 500ms 加 1)更新:
- 接收参数
options = { step = 1000, mode = 'countup' | 'countdown', duration = 60 } - 倒计时模式下,
start()初始化time.value = duration,每次递减step / 1000(单位统一为秒) - 用
Math.max(0, time.value - step / 1000)防止负值,并在归零时自动pause() - 所有数值操作保持对
time.value的直接响应式更新,无需额外triggerRef
暴露控制状态与事件回调(提升可组合性)
仅暴露数值不够 —— 用户常需知道“是否正在运行”、“是否已结束”,或在特定时刻执行逻辑:
- 返回
isRunning = computed(() => timer.value !== null)和isFinished = computed(() => time.value - 支持传入
onFinish: () => void回调,在倒计时归零时触发(注意:在pause()前调用,避免被清理拦截) - 若需监听时间变化做副作用(如触发动画),建议用户用
watch(() => time.value, ...),而非在 Hook 内硬编码
避免常见陷阱(实操避坑指南)
响应式 ref 计时器看似简单,但几个细节极易出错:
-
不要在
start()里直接写setInterval(() => time.value++)—— 闭包会捕获旧的time引用,导致更新无效;应访问time.value实时值 -
不要用
setTimeout递归模拟定时器 —— 累积误差大,且难以精确暂停;优先用setInterval + clearInterval -
重置后再次
start()必须先clearInterval,否则可能残留多个定时器 - 如果组件可能频繁挂载/卸载(如 v-if 切换),确保
onUnmounted清理逻辑稳定执行,推荐用onBeforeUnmount更早介入

















