case 3: Hooks about time
here we mainly introduce three hooks about time: useTimeout
,
useInterval
and useCountDown
useTimeout
useTimeout : execute once in a period of time
pass parameters as long as the function and delay time are needed. It is important to note that
when unloading, OK the timer after clearing it
detailed code:
import { useEffect } from 'react';
import useLatest from '../useLatest';
const useTimeout = (fn:() => void, delay?: number): void => {
const fnRef = useLatest(fn)
useEffect(() => {
if(!delay || delay < 0) return;
const timer = setTimeout(() => {
fnRef.current();
}, delay)
return () => {
clearTimeout(timer)
}
}, [delay])
};
export default useTimeout;
useInterval
useInterval : execute
every time
is roughly the same as useTimeout
, with an extra parameter of whether to render
for the first time immediate
detailed code:
import { useEffect } from 'react';
import useLatest from '../useLatest';
const useInterval = (fn:() => void, delay?: number, immediate?:boolean): void => {
const fnRef = useLatest(fn)
useEffect(() => {
if(!delay || delay < 0) return;
if(immediate) fnRef.current();
const timer = setInterval(() => {
fnRef.current();
}, delay)
return () => {
clearInterval(timer)
}
}, [delay])
};
export default useInterval;
useCountDown
useCountDown : hooks that simply control the countdown
as before, let's think about what this hook needs:
-
the hook for countdown first needs a target time (targetDate), controls the number of
seconds of time change (interval defaults to 1s), and then is the function (onEnd) triggered
after the countdown is completed -
is more clear at a glance. It returns the value of two time differences (time). In more
detail, it can be converted into the corresponding days, hours, minutes (formattedRes)
The return parameter of
detailed code
import { useState, useEffect, useMemo } from 'react';
import useLatest from '../useLatest';
import dayjs from 'dayjs';
type DTime = Date | number | string | undefined;
interface Options {
targetDate?: DTime;
interval?: number;
onEnd?: () => void;
}
interface FormattedRes {
days: number;
hours: number;
minutes: number;
seconds: number;
milliseconds: number;
}
const calcTime = (time: DTime) => {
if(!time) return 0
const res = dayjs(time).valueOf() - new Date().getTime(); //inserting value calculation
if(res < 0) return 0
return res
}
const parseMs = (milliseconds: number): FormattedRes => {
return {
days: Math.floor(milliseconds / 86400000),
hours: Math.floor(milliseconds / 3600000) % 24,
minutes: Math.floor(milliseconds / 60000) % 60,
seconds: Math.floor(milliseconds / 1000) % 60,
milliseconds: Math.floor(milliseconds) % 1000,
};
};
const useCountDown = (options?: Options) => {
const { targetDate, interval = 1000, onEnd } = options || {};
const [time, setTime] = useState(() => calcTime(targetDate));
const onEndRef = useLatest(onEnd);
useEffect(() => {
if(!targetDate) return setTime(0)
setTime(calcTime(targetDate))
const timer = setInterval(() => {
const target = calcTime(targetDate);
setTime(target);
if (target === 0) {
clearInterval(timer);
onEndRef.current?.();
}
}, interval);
return () => clearInterval(timer);
},[targetDate, interval])
const formattedRes = useMemo(() => {
return parseMs(time);
}, [time]);
return [time, formattedRes] as const
};
export default useCountDown;
verify
import React, { useState } from 'react';
import { useCountDown } from '@/components'
import { Button, Toast } from 'antd-mobile';
const Index:React.FC<any> = (props)=> {
const [_, formattedRes] = useCountDown({
targetDate: '2022-12-31 24:00:00',
});
const { days, hours, minutes, seconds, milliseconds } = formattedRes;
const [count, setCount] = useState<number>();
const [countdown] = useCountDown({
targetDate: count,
onEnd: () => {
Toast.show('finish')
},
});
return (
<div style={{padding: 20}}>
<div> There are {days} days {hours} hours {minutes} minutes {seconds} seconds {milliseconds} milliseconds left from 24:00:00 on December 31, 2022</div>
<div>
<p style={{marginTop: 12}}>dynamic change:</p>
<Button color='primary' disabled={countdown !== 0} onClick={() => setCount(Date.now() + 3000)}>
{countdown === 0 ? 'begin' : `still ${Math.round(countdown / 1000)}s`}
</Button>
<Button style={{marginLeft: 8}} onClick={() => setCount(undefined)}>stop</Button>
</div>
</div>
);
}
export default Index;
End
Summary
make a simple summary:
-
A good hooks must have
useMemo
,useCallback
and other api optimizations -
when you encounter passing values in custom hooks, give priority to using
useRef
, and then consider usinguseState
. You can directly useuseLatest
to prevent the value from being not the latest value -
when encapsulating, you should put the stored value into
useRef
, set its initialization through a state, update the corresponding value in judging the circumstances, and make clear the specific meaning of participating parameters, such asuseCreation
anduseEventListener
stocktaking
this article explains 12 custom hooks: usePow
, useLatest
, useCreation
, useMount
, useUnmount
, useUpdate
, useReactive
, useEventListener
, useHover
, useTimeout
, useInterval
, useCountDown
the source of the material here is ahooks, but it is not exactly the same as that of ahooks.Interested partners can knock with their own hands and deepen their understanding by comparing the source code of ahooks
.
I believe that with the help of this article, all of you should have a deeper understanding of Hooks
like me. Of course, practice is the only criterion for testing truth, and knocking on code is the king.