Skip to content

Commit

Permalink
feat: 实现资源到视窗后再加载的功能
Browse files Browse the repository at this point in the history
  • Loading branch information
guaijie committed Jan 20, 2025
1 parent c7bb04c commit d78ffde
Show file tree
Hide file tree
Showing 3 changed files with 116 additions and 14 deletions.
77 changes: 76 additions & 1 deletion packages/hooks/src/useRequest/__tests__/useAutoRunPlugin.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,16 @@
import { act, renderHook, waitFor } from '@testing-library/react';
import useRequest from '../index';
import { request } from '../../utils/testingHelpers';
import useRequest from '../index';

const targetEl = document.createElement('div');
document.body.appendChild(targetEl);

const mockIntersectionObserver = jest.fn().mockReturnValue({
observe: jest.fn(),
disconnect: jest.fn,
});

window.IntersectionObserver = mockIntersectionObserver;

describe('useAutoRunPlugin', () => {
jest.useFakeTimers();
Expand Down Expand Up @@ -284,4 +294,69 @@ describe('useAutoRunPlugin', () => {
expect(hook.result.current.params).toEqual([2]);
expect(fn).toHaveBeenCalledTimes(1);
});

it.only('should work when target is in viewport', async () => {
const obj = { request };

const mockRequest = jest.spyOn(obj, 'request');

hook = setUp(obj.request, {
target: targetEl,
});

const calls = mockIntersectionObserver.mock.calls;
const [onChange] = calls[calls.length - 1];

expect(mockRequest).toHaveBeenCalledTimes(0);
act(() => onChange([{ isIntersecting: true }]));
expect(mockRequest).toHaveBeenCalledTimes(1);
});

it.only('should work once when target is in viewport', async () => {
const obj = { request };

const mockRequest = jest.spyOn(obj, 'request');

hook = setUp(obj.request, {
target: targetEl,
});

const calls = mockIntersectionObserver.mock.calls;
const [onChange] = calls[calls.length - 1];

act(() => onChange([{ isIntersecting: true }]));
act(() => onChange([{ isIntersecting: false }]));
act(() => onChange([{ isIntersecting: true }]));
expect(mockRequest).toHaveBeenCalledTimes(1);
});

it.only('should work when target is in viewport and refreshDeps changed', async () => {
let dep = 1;

const obj = { request };

const mockRequest = jest.spyOn(obj, 'request');

hook = setUp(obj.request, {
refreshDeps: [dep],
target: targetEl,
});

const calls = mockIntersectionObserver.mock.calls;
const [onChange] = calls[calls.length - 1];

act(() => onChange([{ isIntersecting: true }]));
act(() => onChange([{ isIntersecting: false }]));
expect(mockRequest).toHaveBeenCalledTimes(1);

dep = 2;
hook.rerender({
refreshDeps: [dep],
target: targetEl,
});

expect(mockRequest).toHaveBeenCalledTimes(1);
act(() => onChange([{ isIntersecting: true }]));
expect(mockRequest).toHaveBeenCalledTimes(2);
});
});
38 changes: 29 additions & 9 deletions packages/hooks/src/useRequest/src/plugins/useAutoRunPlugin.ts
Original file line number Diff line number Diff line change
@@ -1,43 +1,63 @@
import { useRef } from 'react';
import useInViewport from '../../../useInViewport';
import useUpdateEffect from '../../../useUpdateEffect';
import type { Plugin } from '../types';

// support refreshDeps & ready
const useAutoRunPlugin: Plugin<any, any[]> = (
fetchInstance,
{ manual, ready = true, defaultParams = [], refreshDeps = [], refreshDepsAction },
{
manual,
ready = true,
defaultParams = [],
refreshDeps = [],
target,
root,
rootMargin,
threshold,
refreshDepsAction,
},
) => {
const hasAutoRun = useRef(false);
hasAutoRun.current = false;

const shouldRun = useRef(true);
let [visible] = useInViewport(target, { root, rootMargin, threshold });
if (!target) visible = true;

useUpdateEffect(() => {
shouldRun.current = ready;
}, [ready, ...refreshDeps]);

useUpdateEffect(() => {
if (!manual && ready) {
if (!manual && ready && visible && shouldRun.current) {
hasAutoRun.current = true;
shouldRun.current = false;
fetchInstance.run(...defaultParams);
}
}, [ready]);
}, [ready, visible]);

useUpdateEffect(() => {
if (hasAutoRun.current) {
return;
}
if (!manual) {
if (!manual && visible && shouldRun.current) {
hasAutoRun.current = true;
shouldRun.current = false;
if (refreshDepsAction) {
refreshDepsAction();
} else {
fetchInstance.refresh();
}
}
}, [...refreshDeps]);
}, [...refreshDeps, visible]);

return {
onBefore: () => {
if (!ready) {
return {
stopNow: true,
};
if (target) {
return { stopNow: shouldRun.current || !ready };
}
return { stopNow: !ready };
},
};
};
Expand Down
15 changes: 11 additions & 4 deletions packages/hooks/src/useRequest/src/types.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import type { DependencyList } from 'react';
import type { BasicTarget } from '../../utils/domTarget';
import type Fetch from './Fetch';
import type { CachedData } from './utils/cache';

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

// viewport
target?: BasicTarget | BasicTarget[];
root?: BasicTarget<Element>;
rootMargin?: string;
threshold?: number | number[];

// ready
ready?: boolean;

// [key: string]: any;
}

export type Plugin<TData, TParams extends any[]> = {
(fetchInstance: Fetch<TData, TParams>, options: Options<TData, TParams>): PluginReturn<
TData,
TParams
>;
(
fetchInstance: Fetch<TData, TParams>,
options: Options<TData, TParams>,
): PluginReturn<TData, TParams>;
onInit?: (options: Options<TData, TParams>) => Partial<FetchState<TData, TParams>>;
};

Expand Down

0 comments on commit d78ffde

Please sign in to comment.