react hooks 封装一个countDown 倒计时组件
开发技术
react
, hooks
, ts
, taro

需求分析
需要一个可以按天,时,分和秒来进行倒计时的组件。
简单使用
- 注:主要逻辑请看 useCountDown
import CountDown from '@/components/countDown';
import { useEffect, useState } from 'react';
import Taro from '@tarojs/taro';
const curTime = Date.now()
export default () => {
const [createTime, setCreateTime] = useState<number>(0);
useEffect(() => {
setTimeout(() => {
// 假设:异步获取五分钟前创建的日期
setCreateTime(Date.now() - 5 * 60_000)
}, 1000)
}, [])
return (
<>
{/* 倒计时10秒 */}
<CountDown
value={curTime}
total={10_000}
onChange={v => {
if(v <= 0) Taro.showToast({title: '到时间了', icon: 'none'})
}}
/>
{/* 倒计时3天 */}
<CountDown value={curTime} total={3 * 86400_000} />
{/* 异步获取五分钟前创建的日期 */}
<CountDown value={createTime} total={50 * 60_000} />
</>
)
}
countDown组件的封装
props
传参很简单,只有一个 value
起始时间 , 一个 total
倒计时总时间,还有一个 onChange
返回当前的倒计时剩余时间,当为0的时候就可以进行业务需要的倒计时为0的操作了。
import useCountDown from '@/hooks/useCountDown';
import { formatRemainTime } from '@/utils/format';
import { Text } from '@tarojs/components'
import { useEffect } from 'react';
import './index.less';
type PropsType = {
/** 起始时间 如果不传就是现在的时间 */
value?: number
/** 倒计时时间,默认40min */
total?: number
onChange?: (val: number) => void
}
export default ({value, total = 2400_000, onChange}: PropsType) => {
// 倒计时
const [countDownNum, setCountDown] = useCountDown()
useEffect(() => {
if(value !== void 0) {
setCountDown(value, total)
}
}, [value])
useEffect(() => {
if(countDownNum !== void 0) {
onChange?.(countDownNum)
}
}, [countDownNum])
return (
<Text className='com-count-down'>
{formatRemainTime(countDownNum)}
</Text>
)
}
useCountDown 的封装
首先定义一个计时器变量 timerRef
主要是用于保存/清除计时器;定义一个用来保存一个当前时间的 state
;最后再定义一个参数保存值。
const timerRef = useRef<NodeJS.Timer | undefined>()
const [time, setTime] = useState<number>()
const params = useRef({
beginTime: 0,
total: 0
})
当调用 setCountDown
函数时,开启计时器倒计时,同时需要保存一份函数的参数。
// 开启计时器的函数, beginTime: 输入的初始时间 total: 需要计算的时间
const setCountDown = (beginTime: number, total: number) => {
params.current = { beginTime, total }
clearInterval(timerRef.current)
_setInterval(beginTime, total)
}
开启计时器,根据传入的开始时间,倒计时总时间和当前时间来计算出,当前倒计时的剩余值,如果该值大于0,则表示还有时间可以倒计时。采用 setInterval
每隔一秒触发一次 setTime
,当时间小于0清除计时器即可。
const _setInterval = (beginTime: number, total: number) => {
if(!beginTime || !total) return
const interval = beginTime + total - Date.now()
if(interval < 0) return
setTime(interval)
timerRef.current = setInterval(() => {
setTime(t => {
const _t = t! - 1000
if(_t <= 0) {
clearInterval(timerRef.current)
return 0
}
return _t
})
}, 1000);
}
- 注意一:销毁组件时,记得清除计时器防止内存泄漏
useEffect(() => {
return () => {
clearInterval(timerRef.current)
}
}, [])
- 注意二:由于我这里是用在小程序端的,当小程序退到后台隔几秒后,计时器会自动被停掉,所以当小程序重新展示出来时需要重新开始一下计时器。
useDidShow(() => {
clearInterval(timerRef.current)
_setInterval(params.current.beginTime, params.current.total)
})
useCountDown 完整代码
import { useEffect, useRef, useState } from "react"
import { useDidShow } from "@tarojs/taro"
/** 倒计时钩子 */
const useCountDown = () => {
const timerRef = useRef<NodeJS.Timer | undefined>()
const [time, setTime] = useState<number>()
const params = useRef({
beginTime: 0,
total: 0
})
// 退到后台,大约过个六七秒后定时器会自动暂停掉(回来后自动继续之前的定时器),所以会导致时间不准确
useDidShow(() => {
clearInterval(timerRef.current)
_setInterval(params.current.beginTime, params.current.total)
})
useEffect(() => {
return () => {
clearInterval(timerRef.current)
}
}, [])
// 开启计时器的函数, beginTime: 输入的初始时间 total: 需要计算的时间
const setCountDown = (beginTime: number, total: number) => {
params.current = { beginTime, total }
clearInterval(timerRef.current)
_setInterval(beginTime, total)
}
const _setInterval = (beginTime: number, total: number) => {
if(!beginTime || !total) return
const interval = beginTime + total - Date.now()
if(interval < 0) return
setTime(interval)
timerRef.current = setInterval(() => {
setTime(t => {
const _t = t! - 1000
if(_t <= 0) {
clearInterval(timerRef.current)
return 0
}
return _t
})
}, 1000);
}
// 手动清除计时器
const clearCountDownTimer = () => clearInterval(timerRef.current)
return [time, setCountDown, clearCountDownTimer] as const
}
export default useCountDown
formatRemainTime 格式化剩余时间
根据时间需求:天,时,分和秒定义一个对象数组,包含它们的字符串和对应的时间,遍历该对象数组,处理好天,时,分和秒的情况即可,再在数字前适当添加0即可。
/** 格式化剩余时间 */
export function formatRemainTime(time?: number) {
// 当初始化时间为 undefined 时返回
if(time === void 0) return '0'
const addZero = (n: number) => n >= 10 ? n : ('0' + n)
const timeArr: {s: string, t: number}[] = [
{s: '天', t: 86400},
{s: '时', t: 3600},
{s: '分', t: 60},
{s: '秒', t: 1},
]
time = Math.ceil(time / 1000)
let res = ''
for(let i = 0; i < timeArr.length - 1; i++) {
const item = timeArr[i]
if(time >= item.t) {
const tartget = ~~(time / item.t)
res += (!i ? tartget : addZero(tartget)) + item.s
time %= item.t
}
}
res += addZero(~~(time)) + timeArr.at(-1)!.s
return res
}
总结
回看整个倒计时的代码,感觉代码其实是挺简单的,但是我当时实现起来是花了挺长时间的,主要原因还是我当时对 hooks
不太熟悉,现在回看也觉得写得很一般,不是那么的 hooks
;相信很多大牛都能写得比我好很多,我这里只是分享一下自己曾经写的一个小组件。大家可以按需修改使用。
转载自:https://juejin.cn/post/7176607610555072568