Skip to content

Commit d401890

Browse files
tchentchen-l
tchen
authored andcommitted
feat(useSize): support debounceOptions and throttleOptions(alibaba#2647)
1 parent 299fa2a commit d401890

File tree

7 files changed

+11728
-12773
lines changed

7 files changed

+11728
-12773
lines changed

packages/hooks/src/useSize/__tests__/index.test.tsx

Lines changed: 99 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
1-
import React, { useRef } from 'react';
2-
import { renderHook, act, render, screen } from '@testing-library/react';
3-
import useSize from '../index';
1+
import React, { useRef, act } from "react";
2+
import { renderHook, render, screen } from "@testing-library/react";
3+
import useSize from "../index";
4+
import { sleep } from "../../utils/testingHelpers";
45

56
let callback;
6-
jest.mock('resize-observer-polyfill', () => {
7+
jest.mock("resize-observer-polyfill", () => {
78
return jest.fn().mockImplementation((cb) => {
89
callback = cb;
910
return {
@@ -14,15 +15,15 @@ jest.mock('resize-observer-polyfill', () => {
1415
});
1516

1617
// test about Resize Observer see https://github.com/que-etc/resize-observer-polyfill/tree/master/tests
17-
describe('useSize', () => {
18-
it('should work when target is a mounted DOM', () => {
18+
describe("useSize", () => {
19+
it("should work when target is a mounted DOM", () => {
1920
const hook = renderHook(() => useSize(document.body));
2021
expect(hook.result.current).toEqual({ height: 0, width: 0 });
2122
});
2223

23-
it('should work when target is a `MutableRefObject`', async () => {
24+
it("should work when target is a `MutableRefObject`", async () => {
2425
const mockRaf = jest
25-
.spyOn(window, 'requestAnimationFrame')
26+
.spyOn(window, "requestAnimationFrame")
2627
.mockImplementation((cb: FrameRequestCallback) => {
2728
cb(0);
2829
return 0;
@@ -41,29 +42,33 @@ describe('useSize', () => {
4142
}
4243

4344
render(<Setup />);
44-
expect(await screen.findByText(/^width/)).toHaveTextContent('width: undefined');
45-
expect(await screen.findByText(/^height/)).toHaveTextContent('height: undefined');
45+
expect(await screen.findByText(/^width/)).toHaveTextContent(
46+
"width: undefined"
47+
);
48+
expect(await screen.findByText(/^height/)).toHaveTextContent(
49+
"height: undefined"
50+
);
4651

4752
act(() => callback([{ target: { clientWidth: 10, clientHeight: 10 } }]));
48-
expect(await screen.findByText(/^width/)).toHaveTextContent('width: 10');
49-
expect(await screen.findByText(/^height/)).toHaveTextContent('height: 10');
53+
expect(await screen.findByText(/^width/)).toHaveTextContent("width: 10");
54+
expect(await screen.findByText(/^height/)).toHaveTextContent("height: 10");
5055
mockRaf.mockRestore();
5156
});
5257

53-
it('should not work when target is null', () => {
58+
it("should not work when target is null", () => {
5459
expect(() => {
5560
renderHook(() => useSize(null));
5661
}).not.toThrowError();
5762
});
5863

59-
it('should work', () => {
64+
it("should work", () => {
6065
const mockRaf = jest
61-
.spyOn(window, 'requestAnimationFrame')
66+
.spyOn(window, "requestAnimationFrame")
6267
.mockImplementation((cb: FrameRequestCallback) => {
6368
cb(0);
6469
return 0;
6570
});
66-
const targetEl = document.createElement('div');
71+
const targetEl = document.createElement("div");
6772
const { result } = renderHook(() => useSize(targetEl));
6873

6974
act(() => {
@@ -84,4 +89,82 @@ describe('useSize', () => {
8489

8590
mockRaf.mockRestore();
8691
});
92+
93+
it("debounceOptions should work", async () => {
94+
let count = 0;
95+
96+
function Setup() {
97+
const ref = useRef(null);
98+
const size = useSize(ref, { debounceOptions: { wait: 200 } });
99+
count += 1;
100+
101+
return (
102+
<div ref={ref}>
103+
<div>width: {String(size?.width)}</div>
104+
<div>height: {String(size?.height)}</div>
105+
</div>
106+
);
107+
}
108+
109+
render(<Setup />);
110+
111+
act(() => callback([{ target: { clientWidth: 10, clientHeight: 10 } }]));
112+
act(() => callback([{ target: { clientWidth: 20, clientHeight: 20 } }]));
113+
act(() => callback([{ target: { clientWidth: 30, clientHeight: 30 } }]));
114+
115+
expect(count).toBe(1);
116+
expect(await screen.findByText(/^width/)).toHaveTextContent(
117+
"width: undefined"
118+
);
119+
expect(await screen.findByText(/^height/)).toHaveTextContent(
120+
"height: undefined"
121+
);
122+
123+
await sleep(300);
124+
125+
expect(count).toBe(2);
126+
expect(await screen.findByText(/^width/)).toHaveTextContent("width: 30");
127+
expect(await screen.findByText(/^height/)).toHaveTextContent("height: 30");
128+
});
129+
130+
it("throttleOptions should work", async () => {
131+
let count = 0;
132+
133+
function Setup() {
134+
const ref = useRef(null);
135+
const size = useSize(ref, { throttleOptions: { wait: 500 } });
136+
count += 1;
137+
138+
return (
139+
<div ref={ref}>
140+
<div>width: {String(size?.width)}</div>
141+
<div>height: {String(size?.height)}</div>
142+
</div>
143+
);
144+
}
145+
146+
render(<Setup />);
147+
148+
act(() => callback([{ target: { clientWidth: 10, clientHeight: 10 } }]));
149+
act(() => callback([{ target: { clientWidth: 20, clientHeight: 20 } }]));
150+
act(() => callback([{ target: { clientWidth: 30, clientHeight: 30 } }]));
151+
152+
expect(count).toBe(1);
153+
expect(await screen.findByText(/^width/)).toHaveTextContent(
154+
"width: undefined"
155+
);
156+
expect(await screen.findByText(/^height/)).toHaveTextContent(
157+
"height: undefined"
158+
);
159+
160+
await sleep(450);
161+
expect(count).toBe(2);
162+
expect(await screen.findByText(/^width/)).toHaveTextContent("width: 10");
163+
expect(await screen.findByText(/^height/)).toHaveTextContent("height: 10");
164+
165+
await sleep(200);
166+
expect(count).toBe(3);
167+
expect(await screen.findByText(/^width/)).toHaveTextContent("width: 30");
168+
expect(await screen.findByText(/^height/)).toHaveTextContent("height: 30");
169+
});
87170
});
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
/**
2+
* title: debounce
3+
* desc: useSize can receive debounceOptions as argument
4+
*
5+
* title.zh-CN: 防抖
6+
* desc.zh-CN: useSize 可以接收 debounceOptions 参数
7+
*/
8+
9+
import React, { useRef } from "react";
10+
import { useSize } from "ahooks";
11+
12+
export default () => {
13+
const ref = useRef(null);
14+
const size = useSize(ref, { debounceOptions: { wait: 300 } });
15+
return (
16+
<div ref={ref}>
17+
<p>Try to resize the preview window </p>
18+
<p>
19+
width: {size?.width}px, height: {size?.height}px
20+
</p>
21+
</div>
22+
);
23+
};
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
/**
2+
* title: throttle
3+
* desc: useSize can receive throttleOptions as argument
4+
*
5+
* title.zh-CN: 节流
6+
* desc.zh-CN: useSize 可以接收 throttleOptions 参数
7+
*/
8+
9+
import React, { useRef } from "react";
10+
import { useSize } from "ahooks";
11+
12+
export default () => {
13+
const ref = useRef(null);
14+
const size = useSize(ref, { throttleOptions: { wait: 300 } });
15+
return (
16+
<div ref={ref}>
17+
<p>Try to resize the preview window </p>
18+
<p>
19+
width: {size?.width}px, height: {size?.height}px
20+
</p>
21+
</div>
22+
);
23+
};

packages/hooks/src/useSize/index.en-US.md

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,17 +17,30 @@ A hook that observes size change of an element.
1717

1818
<code src="./demo/demo2.tsx" />
1919

20+
### debounce
21+
22+
<code src="./demo/demo3.tsx" />
23+
24+
### throttle
25+
26+
<code src="./demo/demo4.tsx" />
27+
2028
## API
2129

2230
```typescript
23-
const size = useSize(target);
31+
const size = useSize(target, options?: {
32+
debounceOptions?: DebounceOptions,
33+
throttleOptions?: ThrottleOptions,
34+
});
2435
```
2536

2637
### Params
2738

28-
| Property | Description | Type | Default |
29-
| -------- | ------------------------- | ------------------------------------------------------------- | ------- |
30-
| target | DOM element or ref object | `Element` \| `(() => Element)` \| `MutableRefObject<Element>` | - |
39+
| Property | Description | Type | Default |
40+
| ----------------------- | ------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------- | ------- |
41+
| target | DOM element or ref object | `Element` \| `(() => Element)` \| `MutableRefObject<Element>` | - |
42+
| options.debounceOptions | debounce options(same as useDebounce) | `DebounceOptions` | - |
43+
| options.throttleOptions | throttle options(same as useThrottle), when debounceOptions exists at the same time, debounceOptions is used first. | `ThrottleOptions` | - |
3144

3245
### Result
3346

packages/hooks/src/useSize/index.ts

Lines changed: 41 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,45 @@
1-
import ResizeObserver from 'resize-observer-polyfill';
2-
import useRafState from '../useRafState';
3-
import type { BasicTarget } from '../utils/domTarget';
4-
import { getTargetElement } from '../utils/domTarget';
5-
import useIsomorphicLayoutEffectWithTarget from '../utils/useIsomorphicLayoutEffectWithTarget';
1+
import ResizeObserver from "resize-observer-polyfill";
2+
import useRafState from "../useRafState";
3+
import type { BasicTarget } from "../utils/domTarget";
4+
import { getTargetElement } from "../utils/domTarget";
5+
import useIsomorphicLayoutEffectWithTarget from "../utils/useIsomorphicLayoutEffectWithTarget";
6+
import useDebounceFn from "../useDebounceFn";
7+
import type { DebounceOptions } from "../useDebounce/debounceOptions";
8+
import useMemoizedFn from "../useMemoizedFn";
9+
import useThrottleFn from "../useThrottleFn";
10+
import type { ThrottleOptions } from "../useThrottle/throttleOptions";
611

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

9-
function useSize(target: BasicTarget): Size | undefined {
10-
const [state, setState] = useRafState<Size | undefined>(
11-
() => {
12-
const el = getTargetElement(target);
13-
return el ? { width: el.clientWidth, height: el.clientHeight } : undefined
14-
},
15-
);
14+
function useSize(
15+
target: BasicTarget,
16+
options?: {
17+
debounceOptions?: DebounceOptions;
18+
throttleOptions?: ThrottleOptions;
19+
}
20+
): Size | undefined {
21+
const [state, setState] = useRafState<Size | undefined>(() => {
22+
const el = getTargetElement(target);
23+
return el ? { width: el.clientWidth, height: el.clientHeight } : undefined;
24+
});
25+
26+
const debounce = useDebounceFn(setState, options?.debounceOptions);
27+
28+
const throttle = useThrottleFn(setState, options?.throttleOptions);
29+
30+
const setStateMemoizedFn = useMemoizedFn((nextState: Size) => {
31+
if (options?.debounceOptions) {
32+
debounce.run(nextState);
33+
return;
34+
}
35+
36+
if (options?.throttleOptions) {
37+
throttle.run(nextState);
38+
return;
39+
}
40+
41+
setState(nextState);
42+
});
1643

1744
useIsomorphicLayoutEffectWithTarget(
1845
() => {
@@ -25,7 +52,7 @@ function useSize(target: BasicTarget): Size | undefined {
2552
const resizeObserver = new ResizeObserver((entries) => {
2653
entries.forEach((entry) => {
2754
const { clientWidth, clientHeight } = entry.target;
28-
setState({ width: clientWidth, height: clientHeight });
55+
setStateMemoizedFn({ width: clientWidth, height: clientHeight });
2956
});
3057
});
3158
resizeObserver.observe(el);
@@ -34,7 +61,7 @@ function useSize(target: BasicTarget): Size | undefined {
3461
};
3562
},
3663
[],
37-
target,
64+
target
3865
);
3966

4067
return state;

packages/hooks/src/useSize/index.zh-CN.md

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,17 +17,30 @@ nav:
1717

1818
<code src="./demo/demo2.tsx" />
1919

20+
### 防抖
21+
22+
<code src="./demo/demo3.tsx" />
23+
24+
### 节流
25+
26+
<code src="./demo/demo4.tsx" />
27+
2028
## API
2129

2230
```typescript
23-
const size = useSize(target);
31+
const size = useSize(target, options?: {
32+
debounceOptions?: DebounceOptions,
33+
throttleOptions?: ThrottleOptions,
34+
});
2435
```
2536

2637
### Params
2738

28-
| 参数 | 说明 | 类型 | 默认值 |
29-
| ------ | ---------------- | ------------------------------------------------------------- | ------ |
30-
| target | DOM 节点或者 ref | `Element` \| `(() => Element)` \| `MutableRefObject<Element>` | - |
39+
| 参数 | 说明 | 类型 | 默认值 |
40+
| ----------------------- | ------------------------------------------------------------------------------------ | ------------------------------------------------------------- | ------ |
41+
| target | DOM 节点或者 ref | `Element` \| `(() => Element)` \| `MutableRefObject<Element>` | - |
42+
| options.debounceOptions | 防抖参数(同 useDebounce) | `DebounceOptions` | - |
43+
| options.throttleOptions | 节流参数(同 useThrottle),如果同时配置了 debounceOptions,优先使用 debounceOptions | `ThrottleOptions` | - |
3144

3245
### Result
3346

0 commit comments

Comments
 (0)