Skip to content

Commit d78ffde

Browse files
committed
feat: 实现资源到视窗后再加载的功能
1 parent c7bb04c commit d78ffde

File tree

3 files changed

+116
-14
lines changed

3 files changed

+116
-14
lines changed

packages/hooks/src/useRequest/__tests__/useAutoRunPlugin.test.ts

+76-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,16 @@
11
import { act, renderHook, waitFor } from '@testing-library/react';
2-
import useRequest from '../index';
32
import { request } from '../../utils/testingHelpers';
3+
import useRequest from '../index';
4+
5+
const targetEl = document.createElement('div');
6+
document.body.appendChild(targetEl);
7+
8+
const mockIntersectionObserver = jest.fn().mockReturnValue({
9+
observe: jest.fn(),
10+
disconnect: jest.fn,
11+
});
12+
13+
window.IntersectionObserver = mockIntersectionObserver;
414

515
describe('useAutoRunPlugin', () => {
616
jest.useFakeTimers();
@@ -284,4 +294,69 @@ describe('useAutoRunPlugin', () => {
284294
expect(hook.result.current.params).toEqual([2]);
285295
expect(fn).toHaveBeenCalledTimes(1);
286296
});
297+
298+
it.only('should work when target is in viewport', async () => {
299+
const obj = { request };
300+
301+
const mockRequest = jest.spyOn(obj, 'request');
302+
303+
hook = setUp(obj.request, {
304+
target: targetEl,
305+
});
306+
307+
const calls = mockIntersectionObserver.mock.calls;
308+
const [onChange] = calls[calls.length - 1];
309+
310+
expect(mockRequest).toHaveBeenCalledTimes(0);
311+
act(() => onChange([{ isIntersecting: true }]));
312+
expect(mockRequest).toHaveBeenCalledTimes(1);
313+
});
314+
315+
it.only('should work once when target is in viewport', async () => {
316+
const obj = { request };
317+
318+
const mockRequest = jest.spyOn(obj, 'request');
319+
320+
hook = setUp(obj.request, {
321+
target: targetEl,
322+
});
323+
324+
const calls = mockIntersectionObserver.mock.calls;
325+
const [onChange] = calls[calls.length - 1];
326+
327+
act(() => onChange([{ isIntersecting: true }]));
328+
act(() => onChange([{ isIntersecting: false }]));
329+
act(() => onChange([{ isIntersecting: true }]));
330+
expect(mockRequest).toHaveBeenCalledTimes(1);
331+
});
332+
333+
it.only('should work when target is in viewport and refreshDeps changed', async () => {
334+
let dep = 1;
335+
336+
const obj = { request };
337+
338+
const mockRequest = jest.spyOn(obj, 'request');
339+
340+
hook = setUp(obj.request, {
341+
refreshDeps: [dep],
342+
target: targetEl,
343+
});
344+
345+
const calls = mockIntersectionObserver.mock.calls;
346+
const [onChange] = calls[calls.length - 1];
347+
348+
act(() => onChange([{ isIntersecting: true }]));
349+
act(() => onChange([{ isIntersecting: false }]));
350+
expect(mockRequest).toHaveBeenCalledTimes(1);
351+
352+
dep = 2;
353+
hook.rerender({
354+
refreshDeps: [dep],
355+
target: targetEl,
356+
});
357+
358+
expect(mockRequest).toHaveBeenCalledTimes(1);
359+
act(() => onChange([{ isIntersecting: true }]));
360+
expect(mockRequest).toHaveBeenCalledTimes(2);
361+
});
287362
});

packages/hooks/src/useRequest/src/plugins/useAutoRunPlugin.ts

+29-9
Original file line numberDiff line numberDiff line change
@@ -1,43 +1,63 @@
11
import { useRef } from 'react';
2+
import useInViewport from '../../../useInViewport';
23
import useUpdateEffect from '../../../useUpdateEffect';
34
import type { Plugin } from '../types';
45

56
// support refreshDeps & ready
67
const useAutoRunPlugin: Plugin<any, any[]> = (
78
fetchInstance,
8-
{ manual, ready = true, defaultParams = [], refreshDeps = [], refreshDepsAction },
9+
{
10+
manual,
11+
ready = true,
12+
defaultParams = [],
13+
refreshDeps = [],
14+
target,
15+
root,
16+
rootMargin,
17+
threshold,
18+
refreshDepsAction,
19+
},
920
) => {
1021
const hasAutoRun = useRef(false);
1122
hasAutoRun.current = false;
1223

24+
const shouldRun = useRef(true);
25+
let [visible] = useInViewport(target, { root, rootMargin, threshold });
26+
if (!target) visible = true;
27+
28+
useUpdateEffect(() => {
29+
shouldRun.current = ready;
30+
}, [ready, ...refreshDeps]);
31+
1332
useUpdateEffect(() => {
14-
if (!manual && ready) {
33+
if (!manual && ready && visible && shouldRun.current) {
1534
hasAutoRun.current = true;
35+
shouldRun.current = false;
1636
fetchInstance.run(...defaultParams);
1737
}
18-
}, [ready]);
38+
}, [ready, visible]);
1939

2040
useUpdateEffect(() => {
2141
if (hasAutoRun.current) {
2242
return;
2343
}
24-
if (!manual) {
44+
if (!manual && visible && shouldRun.current) {
2545
hasAutoRun.current = true;
46+
shouldRun.current = false;
2647
if (refreshDepsAction) {
2748
refreshDepsAction();
2849
} else {
2950
fetchInstance.refresh();
3051
}
3152
}
32-
}, [...refreshDeps]);
53+
}, [...refreshDeps, visible]);
3354

3455
return {
3556
onBefore: () => {
36-
if (!ready) {
37-
return {
38-
stopNow: true,
39-
};
57+
if (target) {
58+
return { stopNow: shouldRun.current || !ready };
4059
}
60+
return { stopNow: !ready };
4161
},
4262
};
4363
};

packages/hooks/src/useRequest/src/types.ts

+11-4
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import type { DependencyList } from 'react';
2+
import type { BasicTarget } from '../../utils/domTarget';
23
import type Fetch from './Fetch';
34
import type { CachedData } from './utils/cache';
45

@@ -87,17 +88,23 @@ export interface Options<TData, TParams extends any[]> {
8788
retryCount?: number;
8889
retryInterval?: number;
8990

91+
// viewport
92+
target?: BasicTarget | BasicTarget[];
93+
root?: BasicTarget<Element>;
94+
rootMargin?: string;
95+
threshold?: number | number[];
96+
9097
// ready
9198
ready?: boolean;
9299

93100
// [key: string]: any;
94101
}
95102

96103
export type Plugin<TData, TParams extends any[]> = {
97-
(fetchInstance: Fetch<TData, TParams>, options: Options<TData, TParams>): PluginReturn<
98-
TData,
99-
TParams
100-
>;
104+
(
105+
fetchInstance: Fetch<TData, TParams>,
106+
options: Options<TData, TParams>,
107+
): PluginReturn<TData, TParams>;
101108
onInit?: (options: Options<TData, TParams>) => Partial<FetchState<TData, TParams>>;
102109
};
103110

0 commit comments

Comments
 (0)