背景
场景需要动态开启轮询,根据场景停止或启动。直接使用 setInterval 并不是特别方便,还要在组件卸载/重新render时清理定时器。于是考虑将 setInterval 封装为 hooks ,简化使用。
功能设计
期望它是一个功能函数,接受参数为 fn 回调,返回值为触发函数 run 和定时器清理函数 clear 。
功能实现
同样的,首先考虑入参, fn 回调与 delay 时间间隔是首要参数。为了方便使用,同时不对 ahooks 库的 useInterval 产生混淆。这里同样对它支持 option 参数。
export interface UseCreateIntervalProps {
fn: () => void,
delay: number;
options?: {
immediate?: boolean;
};
}
在 hook 设计过程中,考虑到 run 函数可能会重复调用,但是每次 run 都会产生一个定时器,这是不可接受的。于是在 options 中增加参数 autoClear,在每次调用 run 时清理之前废弃定时器。好的,废话不多说,下面贴代码。
import { useMemoizedFn } from 'ahooks';
import { isNumber } from 'lodash';
import { useCallback, useRef } from 'react';
export interface UseCreateIntervalOptions {
immediate?: boolean;
autoClear?: boolean;
}
export interface UseCreateIntervalProps {
fn: () => void,
delay?: number;
options?: UseCreateIntervalOptions;
}
export const useCreateInterval = ({
fn,
delay = 1000,
options = {
immediate: true,
autoClear: true,
},
}: UseCreateIntervalProps) => {
const timerCallback = useMemoizedFn(fn);
const timerRef = useRef<NodeJS.Timeout>();
const clear = useCallback(() => {
if (timerRef.current) {
clearInterval(timerRef.current);
}
}, []);
const run = useCallback(() => {
if (!isNumber(delay) || delay < 0) {
return;
}
if (options?.autoClear) {
clear();
}
if (options?.immediate) {
timerCallback();
}
timerRef.current = setInterval(timerCallback, delay);
return clear;
}, [delay, options]);
return {
run,
clear,
};
};
export default useCreateInterval;
使用示例
上面封装完成,有点工厂函数的感觉,需要先通过 useCreateInterval 得到我们的目标函数,然后再进行调用。示例如下。
const test = (fn: () => void) => {
const { run, clear } = useCreateInterval({ fn, delay: 5000 });
useEffect(() => run(), []);
}
扩展 useCreateInterval
上面已经能够很灵活且方便地使用 interval 了,但我就是想用类似 effefct 的形式调用,连 useEffect 我都不想写怎么办呢?这里考虑增加参数 active, 通过控制 active 来控制定时器的启动或停止。实现如下。
export interface UseEffectIntervalProps extends UseCreateIntervalProps {
active: boolean;
}
export const useEffectInterval = ({ active, ...restProps }: UseEffectIntervalProps) => {
const { run, clear } = useCreateInterval(restProps);
useEffect(() => active ? run() : clear(), [active]);
};
export default useEffectInterval;
举一反三
根据上面的设计,可以很轻松的复刻出 setTimeout 的封装。根据他俩的实际应用场景,只需要细微的更改即可。下面是代码实例。
import { useMemoizedFn } from 'ahooks';
import { isNumber } from 'lodash';
import { useCallback, useRef } from 'react';
export interface UseCreateTimeoutOptions {
autoClear?: boolean;
}
export interface UseCreateTimeoutProps {
fn: () => void,
delay?: number;
options?: UseCreateTimeoutOptions;
}
export const useCreateTimeout = ({
fn,
delay = 1000,
options = {
autoClear: true,
},
}: UseCreateTimeoutProps) => {
const timerCallback = useMemoizedFn(fn);
const timerRef = useRef<NodeJS.Timeout>();
const clear = useCallback(() => {
if (timerRef.current) {
clearTimeout(timerRef.current);
}
}, []);
const run = useCallback(() => {
if (!isNumber(delay) || delay < 0) {
return;
}
if (options?.autoClear) {
clear();
}
timerRef.current = setTimeout(timerCallback, delay);
return clear;
}, [delay, options]);
return {
run,
clear,
};
};
export default useCreateTimeout;
扩展 useCreateTimeout
根据 setTimeout 的特点,当启用 timeout 定时器时,它是在指定时间后执行回调。推断我们业务中更多是按需启动定时器,于是这里 active 参数定义为 any, 当它产生变化时即可启用。示例如下。
import { useEffect } from 'react';
import { UseCreateTimeoutProps, useCreateTimeout } from './useCreateTimeout';
export interface UseEffectTimeoutProps extends UseCreateTimeoutProps {
active: any;
}
export const useEffectTimeout = ({ active, ...restProps }: UseEffectTimeoutProps) => {
const { run, clear } = useCreateTimeout(restProps);
useEffect(() => {
return active ? run() : clear();
}, [active]);
};
export default useEffectTimeout;