实现精简版useSize

实现精简版useSize

ahooks是阿里开源的一套 React Hooks 库,里面封装了大量好用的 Hooks,最近也在项目中频繁使用到了库中useSize这个钩子函数,于是就学习了一下它的源码实现,并且实现了一个精简版useSize

要点一:ResizeObserver监听 Element 内容区域的边界框改变

要点二:requestAnimationFrame优化高频刷新情况下的数据更新

代码实现

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
import {
MutableRefObject,
useCallback,
useEffect,
useLayoutEffect,
useRef,
useState,
} from "react";

type TargetValue<T> = T | undefined | null;

type TargetType = HTMLElement | Element | Window | Document;

export type BasicTarget<T extends TargetType = Element> =
| (() => TargetValue<T>)
| TargetValue<T>
| MutableRefObject<TargetValue<T>>;

type Size = { width: number; height: number };

export default function useSize(target: BasicTarget): Size | undefined {
const [state, setState] = useState<Size | undefined>();
const ref = useRef(0);
const [resizeObserver, setResizeObserver] = useState<ResizeObserver>();

const setRafState = useCallback(
(value: Size | ((prevState?: Size) => Size)) => {
cancelAnimationFrame(ref.current);

ref.current = requestAnimationFrame(() => {
setState(value);
});
},
[]
);

useEffect(
() => () => {
resizeObserver?.disconnect();
cancelAnimationFrame(ref.current);
},
[]
);

useLayoutEffect(() => {
if (!target) return;

let targetEl: TargetValue<TargetType>;
if ("current" in target) {
targetEl = target.current;
} else if (typeof target === "function") {
targetEl = target();
} else {
targetEl = target;
}

if (!targetEl) return;

const resizeObserver = new ResizeObserver((entries) => {
entries.forEach((entry) => {
const { clientWidth, clientHeight } = entry.target;

setRafState({
width: clientWidth,
height: clientHeight,
});
});
});

setResizeObserver(resizeObserver);

resizeObserver?.observe(targetEl);
}, []);

return state;
}

具体使用

1
2
3
4
5
6
7
8
9
10
11
12
13
export default function App() {
const ref = useRef(null);
const size = useSize(ref);

return (
<div ref={ref} style={{ border: "1px solid red" }}>
<p>Try to resize the preview window </p>
<p>
width: {size?.width}px, height: {size?.height}px
</p>
</div>
);
}

仅作为自己的学习积累,有什么需要改进的地方请大家多多批评指点 😅!

评论