Skip to content

Commit 30138b3

Browse files
committed
feat: add com
1 parent 32e0aee commit 30138b3

File tree

5 files changed

+174
-5
lines changed

5 files changed

+174
-5
lines changed

src/components/index.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,3 +3,6 @@ export type { MakeGlobalApiOptions, MakeGlobalApiReturnType } from './make-globa
33

44
export { default as CacheComponentDOM } from './cache-component-dom';
55
export type { CacheComponentDOMProps } from './cache-component-dom';
6+
7+
export { default as LoadScript } from './load-script';
8+
export { LoadScriptProps } from './load-script';

src/components/load-script/index.tsx

Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
import React from 'react';
2+
import RPT from 'prop-types';
3+
4+
export interface LoadScriptProps {
5+
attributes: Partial<HTMLScriptElement>;
6+
onCreate: () => void;
7+
onError: () => void;
8+
onLoad: () => void;
9+
url: string;
10+
}
11+
12+
class LoadScript extends React.Component<LoadScriptProps> {
13+
static propTypes = {
14+
attributes: RPT.object,
15+
onCreate: RPT.func,
16+
onError: RPT.func.isRequired,
17+
onLoad: RPT.func.isRequired,
18+
url: RPT.string.isRequired,
19+
};
20+
21+
static defaultProps: Partial<LoadScriptProps> = {
22+
onCreate: () => {},
23+
onError: () => {},
24+
onLoad: () => {},
25+
};
26+
27+
static scriptObservers: {
28+
[url: string]: {
29+
[scriptLoaderId: string]: LoadScriptProps;
30+
};
31+
} = {};
32+
33+
static loadedScripts: Record<string, boolean> = {};
34+
35+
static erroredScripts: Record<string, boolean> = {};
36+
37+
static idCount = 0;
38+
39+
scriptLoaderId: string = '';
40+
41+
constructor(props: LoadScriptProps) {
42+
super(props);
43+
this.scriptLoaderId = `id${LoadScript.idCount++}`; // eslint-disable-line space-unary-ops, no-plusplus
44+
}
45+
46+
componentDidMount() {
47+
const { onError, onLoad, url } = this.props;
48+
49+
if (LoadScript.loadedScripts[url]) {
50+
onLoad();
51+
return;
52+
}
53+
54+
if (LoadScript.erroredScripts[url]) {
55+
onError();
56+
return;
57+
}
58+
59+
if (LoadScript.scriptObservers[url]) {
60+
LoadScript.scriptObservers[url][this.scriptLoaderId] = this.props;
61+
return;
62+
}
63+
64+
LoadScript.scriptObservers[url] = {
65+
[this.scriptLoaderId]: this.props,
66+
};
67+
68+
this.createScript();
69+
}
70+
71+
componentWillUnmount() {
72+
const { url } = this.props;
73+
const observers = LoadScript.scriptObservers[url];
74+
75+
if (observers) {
76+
delete observers[this.scriptLoaderId];
77+
}
78+
}
79+
80+
createScript() {
81+
const { onCreate, url, attributes } = this.props;
82+
const script = document.createElement('script');
83+
84+
onCreate();
85+
86+
if (attributes) {
87+
Object.keys(attributes).forEach((prop) => {
88+
script.setAttribute(prop, attributes[prop as keyof HTMLScriptElement] as string);
89+
});
90+
}
91+
92+
script.src = url;
93+
94+
if (!script.hasAttribute('async')) {
95+
script.async = true;
96+
}
97+
98+
const callObserverFuncAndRemoveObserver = (
99+
shouldRemoveObserver: (observer: LoadScriptProps) => boolean,
100+
) => {
101+
const observers = LoadScript.scriptObservers[url];
102+
Object.keys(observers).forEach((key) => {
103+
if (shouldRemoveObserver(observers[key])) {
104+
delete LoadScript.scriptObservers[url][this.scriptLoaderId];
105+
}
106+
});
107+
};
108+
script.onload = () => {
109+
LoadScript.loadedScripts[url] = true;
110+
callObserverFuncAndRemoveObserver((observer) => {
111+
observer.onLoad();
112+
return true;
113+
});
114+
};
115+
116+
script.onerror = () => {
117+
LoadScript.erroredScripts[url] = true;
118+
callObserverFuncAndRemoveObserver((observer) => {
119+
observer.onError();
120+
return true;
121+
});
122+
};
123+
124+
document.body.appendChild(script);
125+
}
126+
127+
render() {
128+
return null;
129+
}
130+
}
131+
132+
export default LoadScript;

src/hooks/index.ts

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,16 +8,12 @@ export { default as useMount } from './useMount';
88
export { default as useUnmount } from './useUnmount';
99
export { default as useSafeState } from './useSafeState';
1010
export { default as useMountedState } from './useMountedState';
11-
export { default as usePersistFn } from './usePersistFn';
1211
export { default as useUpdateEffect } from './useUpdateEffect';
1312
export { default as useForceUpdate } from './useForceUpdate';
1413
export { default as useDeepEffect } from './useDeepEffect';
1514
export { default as useControlledState } from './useControlledState';
1615
export { default as useDeepCompareMemoize } from './useDeepCompareMemoize';
1716
export { default as useDeepCompareEffect } from './useDeepCompareEffect';
18-
export { default as useMergeState } from './useMergeState';
19-
export { default as useBoolean } from './useBoolean';
20-
export { default as useToggle } from './useToggle';
2117

2218
// bom
2319
export { default as useTimeout } from './useTimeout';
@@ -37,14 +33,21 @@ export { default as useSize } from './useSize';
3733
export { default as useSet } from './useSet';
3834
export { default as useMap } from './useMap';
3935
export { default as useError } from './useError';
36+
export { default as useMergeState } from './useMergeState';
37+
export { default as useBoolean } from './useBoolean';
38+
export { default as useToggle } from './useToggle';
39+
export { default as usePrevious } from './usePrevious';
4040
export { default as useCreation } from './useCreation';
41+
export { default as usePersistFn } from './usePersistFn';
4142

4243
// async
4344
export { default as useAsync } from './useAsync';
4445
export { default as useAsyncFn } from './useAsyncFn';
4546
export { default as useAsyncRetry } from './useAsyncRetry';
4647
export { default as useUntil } from './useUntil';
48+
export { default as usePromise } from './usePromise';
4749

50+
// event
4851
export { default as useDebounce } from './useDebounce';
4952
export { default as useDebounceEffect } from './useDebounceEffect';
5053
export { default as useDebounceFn } from './useDebounceFn';
@@ -53,4 +56,3 @@ export { default as useThrottleEffect } from './useThrottleEffect';
5356
export { default as useThrottleFn } from './useThrottleFn';
5457
export { default as useCounter } from './useCounter';
5558
export { default as useCountdown } from './useCountdown';
56-
export { default as usePrevious } from './usePrevious';

src/hooks/usePrevious.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,11 @@ import { useRef } from 'react';
22

33
export type compareFunction<T> = (prev: T | undefined, next: T) => boolean;
44

5+
/**
6+
* 使用上一次的状态
7+
* @param state
8+
* @param compare
9+
*/
510
function usePrevious<T>(state: T, compare?: compareFunction<T>): T | undefined {
611
const prevRef = useRef<T>();
712
const curRef = useRef<T>();

src/hooks/usePromise.ts

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import { useCallback } from 'react';
2+
import useMountedState from './useMountedState';
3+
4+
export type UsePromise = () => <T>(promise: Promise<T>) => Promise<T>;
5+
6+
/**
7+
* 包裹promise,组件卸载阻止更新视图
8+
*/
9+
const usePromise: UsePromise = () => {
10+
const isMounted = useMountedState();
11+
return useCallback(
12+
(promise: Promise<any>) =>
13+
new Promise<any>((resolve, reject) => {
14+
const onValue = (value: any) => {
15+
isMounted() && resolve(value);
16+
};
17+
const onError = (error: any) => {
18+
isMounted() && reject(error);
19+
};
20+
promise.then(onValue, onError);
21+
}),
22+
// eslint-disable-next-line react-hooks/exhaustive-deps
23+
[],
24+
);
25+
};
26+
27+
export default usePromise;

0 commit comments

Comments
 (0)