引言
在现代 Web 开发 中,倒计时功能已成为提升用户体验的关键组件之一。无论是在电商平台的限时抢购、社交媒体的实时互动,还是在线服务的验证码获取,倒计时功能都在其中扮演着至关重要的角色。它们不仅能够提醒用户注意时间限制,还能增加紧迫感,促使用户采取行动。然而,传统的倒计时实现方式可能会遇到性能瓶颈、难以维护和复用等问题。
针对这些挑战,本文将深入探讨如何在 Vue 3 项目中封装一个高效且易于维护的倒计时 Hook ------ useCountdown
,以解决传统方法的局限。
实践体验
useCountdown
Hook 实现
ts
import { ref, unref, onUnmounted } from "vue";
export function useCountdown(duration: number, onCountdownEnd?: () => void) {
const time = ref(duration);
const isTiming = ref(false);
let timer: ReturnType<typeof setInterval> | null;
const clear = () => {
if (timer) {
clearInterval(timer);
timer = null;
}
}
const stop = () => {
clear();
isTiming.value = false;
}
const reset = () => {
stop();
time.value = duration;
}
const start = () => {
if (unref(isTiming) || !!timer) {
return;
}
isTiming.value = true;
timer = setInterval(() => {
if (unref(time) <= 0) {
reset();
onCountdownEnd && onCountdownEnd()
} else {
time.value--;
}
}, 1000)
}
onUnmounted(() => {
reset()
})
return {time, isTiming, start, stop, reset} as const;
}
useCountdown
Hook 思路
倒计时 Hook 需满足以下核心功能需求,以实现灵活且可定制的倒计时行为:
- 总时长设定(
duration
) :- 用户需提供倒计时的总时长(
duration
),以秒为单位。
- 用户需提供倒计时的总时长(
- 倒计时结束回调(
onCountdownEnd
) :- 当倒计时结束时,触发用户定义的回调函数(
onCountdownEnd
)。
- 当倒计时结束时,触发用户定义的回调函数(
- 返回值 :
- Hook需返回以下两个值:
time
:剩余的倒计时时长。isTiming
:表示倒计时是否正在进行中。
- Hook需返回以下两个值:
- 控制方法 :
- 提供以下方法以控制倒计时行为:
start
:开始倒计时。stop
:停止倒计时。reset
:重置倒计时至初始状态。
- 提供以下方法以控制倒计时行为:
通过这些方法,用户可以精确控制倒计时的启动、停止和重置,使其更好地适应不同的业务场景和需求.
useCountdown
Hook 应用实践
获取验证码按钮
- 获取验证码按钮需要满足以下需求
- 自定义倒计时时长 :通过
duration
属性,您可以设定倒计时的持续时间。 - 自动禁用状态 :
disabled
属性结合倒计时状态isTiming
自动处理按钮的禁用状态,防止用户在倒计时期间重复点击。 - 异步校验方法 :
validateBeforeSendingCode
属性允许您定义一个异步函数,用于在倒计时开始前进行用户自定义的前置校验和发送验证码的API调用。 - 自定义展示内容 :支持
slot
分发,允许您自定义按钮显示内容,同时提供time
和isTiming
属性供您使用。
- 自定义倒计时时长 :通过
ts
<script setup lang="ts">
import { useCountdown } from "../hooks/useCountdown";
// https://code.juejin.cn/api/raw/7445202794878861348?id=7445202794878877732
import { computed, PropType } from "vue@3.5.13";
const props = defineProps({
duration: {
type: Number,
default: 60
},
disabled: {
type: Boolean,
default: false
},
validateBeforeSendingCode: {
type: Function as PropType<()=>Promise<boolean>>,
default: ()=>Promise.resolve(true)
}
})
const { time, isTiming, start } = useCountdown(props.duration);
const isDisabled = computed(()=>{
return props.disabled || isTiming.value;
})
const handleClick = async () => {
const res = await props.validateBeforeSendingCode();
res && start();
}
</script>
<template>
<button :disabled="isDisabled" @click="handleClick">
<slot :time="time" :isTiming="isTiming"></slot>
<template v-if="!$slots.default">
{{isTiming ? `${time}s 后重试` : "获取验证码"}}
</template>
</button>
</template>
<style scoped>
button {
padding: 10px 20px;
border: none;
border-radius: 4px;
background-color: #409eff;
color: #ffffff;
font-size: 14px;
cursor: pointer;
outline: none;
}
button+button {
margin-left: 10px;
}
button:hover {
background-color: #66b1ff;
}
button:active {
background-color: #288ae2;
}
button[disabled] {
cursor: not-allowed;
color: #fff;
background-color: #a0cfff;
border-color: #a0cfff;
}
</style>
倒计时自动登录
倒计时结束,触发用户自定义回调来实现
ts
<script setup lang="ts">
import { useCountdown } from "../hooks/useCountdown"
// https://code.juejin.cn/api/raw/7445202794878861348?id=7445202794878877732
const handleCountdownEnd = () => {
reset(); // 重置倒计时
// do something
// 跳转逻辑
}
const { time, reset, start } = useCountdown(3, handleCountdownEnd);
start();
</script>
<template>
<button @click="handleCountdownEnd">{{time}}s 自动跳转登录</button>
</template>
<style scoped>
</style>
感谢阅读,敬请斧正!