# useThrottle
节流hook。如果直接在函数组件里面给某个方法添加节流:const func = _.throttle(fn, 500),那么在组件更新时导致节流函数成为一个新的函数,内部的时间变量重置,导致节流失效。防抖同理。
import _ from 'lodash';
import { useCallback, useEffect, useRef } from 'react';
export default function useThrottle(fn: Function, delay: number, dep: any[] = []) {
const fnRef = useRef<Function>(fn);
const options = { leading: true, trailing: false };
useEffect(() => {
fnRef.current = fn;
}, [fn]);
return useCallback(
_.throttle((...args) => fnRef.current(...args), delay, options),
dep,
);
}
// 不借助其他工具函数
/*
import { useCallback, useEffect, useRef } from 'react';
interface ThrottleOptions {
delay: number;
leading?: boolean; // 指定调用在节流开始前。
trailing?: boolean; // 指定调用在节流结束后。
}
export default function useThrottle<T extends Function>(fn: T, opt: ThrottleOptions, dep: any[] = []) {
const { delay, leading = true, trailing = true } = opt;
const { current } = useRef<{ fn: T; timer: NodeJS.Timeout | null; startTime: number }>({
fn,
timer: null,
startTime: new Date().getTime(),
});
const leadingRef = useRef({ leading: leading });
useEffect(() => {
current.fn = fn;
}, [fn]);
return useCallback<T>(((...args) => {
const curTime = new Date().getTime();
const remainTime = delay - (curTime - current.startTime);
if (current.timer) {
clearTimeout(current.timer);
current.timer = null;
}
if (remainTime <= 0) {
// 开始时是否执行
if (leadingRef.current.leading) {
current.fn.call(null, ...args);
} else {
leadingRef.current.leading = true;
}
current.startTime = new Date().getTime();
} else if (trailing) {
// 结束时是否执行
current.timer = setTimeout(() => {
current.fn.call(null, ...args);
current.startTime = new Date().getTime();
}, remainTime);
}
}) as unknown as T, dep);
}
*/
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
# useDebounce
import _ from 'lodash';
import { useCallback, useEffect, useRef } from 'react';
export default function useDebounce(fn: Function, delay: number, dep: any[] = []) {
const fnRef = useRef<Function>(fn);
const options = { leading: true };
useEffect(() => {
fnRef.current = fn;
}, [fn]);
return useCallback(
_.debounce((...args) => fnRef.current(...args), delay, options),
dep,
);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# useWindowSize
监听窗口尺寸的变化。
import _ from 'lodash';
import { useEffect, useRef, useState } from 'react';
interface WindowSize {
winWidth: number;
winHeight: number;
}
function useWindowSize(): WindowSize {
const isAddListener = useRef(false);
const [windowSize, setWindowSize] = useState<WindowSize>({
winWidth: window.innerWidth,
// winWidth: 0,
winHeight: window.innerHeight,
// winHeight: 0,
});
useEffect(() => {
if (isAddListener.current) {
return;
}
const handler = _.throttle(
() => {
setWindowSize({
winWidth: window.innerWidth,
winHeight: window.innerHeight,
});
},
200,
{
leading: true,
trailing: true,
},
);
handler();
window.addEventListener('resize', handler);
isAddListener.current = true;
// eslint-disable-next-line consistent-return
return () => {
window.removeEventListener('resize', handler);
};
}, []);
return windowSize;
}
export default useWindowSize;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
# useJudgeMobile
通过监听窗口尺寸变化,判断是否显示移动端页面。
import useWindowSize from './useWindowSize';
function useJudgeMobile() {
const { winWidth } = useWindowSize();
return winWidth <= 768; // 自定义尺寸
}
export default useJudgeMobile;
1
2
3
4
5
6
7
8
9
2
3
4
5
6
7
8
9
# useCountDown
倒计时。
import { useState, useEffect, useRef } from 'react';
interface CountDownReturn {
remainTime: number;
startCountDown: (time: number) => void;
stopCountDown: () => void
}
interface CountDownParams {
time?: number;
speed?: number;
}
/**
* @description 注意:时间单位都为秒
* @author fengchunqi
* @date 2023-12-21 10:44:57
* @export
* @param {CountDownParams} options
*/
export default function useCountDown(options: CountDownParams): CountDownReturn {
const { time = 0, speed = 1 } = options;
const [baseTime, setBaseTime] = useState({ time });
const [remainTime, setRemainTime] = useState(0);
const timer = useRef<{
startTime: number;
counter: number;
timer: NodeJS.Timeout | null
}>({
startTime: 0,
counter: 0,
timer: null,
});
const stopTimer = () => {
if (timer.current.timer) {
clearTimeout(timer.current.timer);
timer.current.timer = null;
}
};
/**
* @param time seconds
*/
const startTimer = () => {
const frameFunc = () => {
const { startTime, counter } = timer.current;
const speedMs = speed * 1000; // 转成毫秒
const real = counter * speedMs;
const ideal = new Date().getTime() - startTime;
timer.current.counter++;
const diff = ideal - real;
const remain = baseTime.time - counter * speed;
setRemainTime(remain);
if (remain > 0) {
/*
* 程序切换后台后js暂停执行,切回页面后倒计时不继续执行,需去掉误差补偿
* 因为实际时间很大导致diff很大,setTimeout传入的时间为负数无法执行
*/
timer.current.timer = setTimeout(frameFunc, speedMs - diff); // 误差补偿
} else {
stopTimer();
}
};
timer.current.timer = setTimeout(frameFunc, 0);
};
const resetCountDown = () => {
stopTimer();
timer.current = {
startTime: new Date().getTime(),
counter: 0,
timer: null,
};
if (baseTime.time > 0) {
startTimer();
}
};
useEffect(() => {
resetCountDown();
return (() => {
stopTimer();
});
}, [baseTime]);
const startCountDown = (countDown: number) => {
setBaseTime(() => ({ time: countDown }));
};
const stopCountDown = () => {
stopTimer();
};
return {
remainTime,
startCountDown,
stopCountDown,
};
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
# useMount
import { EffectCallback, useEffect } from 'react';
const useMount = (callback: EffectCallback) => {
useEffect(callback, []);
};
export default useMount;
1
2
3
4
5
6
7
2
3
4
5
6
7
# useUnmount
import { useEffect, useRef } from 'react';
const useUnmount = (callback: Function) => {
const callbackRef = useRef(callback);
callbackRef.current = callback;
useEffect(() => () => {
callbackRef.current();
}, []);
};
export default useUnmount;
1
2
3
4
5
6
7
8
9
10
11
12
13
2
3
4
5
6
7
8
9
10
11
12
13