开发技术
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?: numberonChange?: (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) returnconst interval = beginTime + total - Date.now()if(interval < 0) returnsetTime(interval)timerRef.current = setInterval(() => {setTime(t => {const _t = t! - 1000if(_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) returnconst interval = beginTime + total - Date.now()if(interval < 0) returnsetTime(interval)timerRef.current = setInterval(() => {setTime(t => {const _t = t! - 1000if(_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.stime %= item.t}}res += addZero(~~(time)) + timeArr.at(-1)!.sreturn res
}
总结
回看整个倒计时的代码,感觉代码其实是挺简单的,但是我当时实现起来是花了挺长时间的,主要原因还是我当时对 hooks
不太熟悉,现在回看也觉得写得很一般,不是那么的 hooks
;相信很多大牛都能写得比我好很多,我这里只是分享一下自己曾经写的一个小组件。大家可以按需修改使用。
最后
最近找到一个VUE的文档,它将VUE的各个知识点进行了总结,整理成了《Vue 开发必须知道的36个技巧》。内容比较详实,对各个知识点的讲解也十分到位。
有需要的小伙伴,可以点击下方卡片领取,无偿分享