donar 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (106) hide show
  1. package/.cspell/custom-dictionary.txt +5 -0
  2. package/.github/actions/base-check/action.yaml +27 -0
  3. package/.github/actions/env-setup/action.yaml +74 -0
  4. package/.github/workflows/pr-check.yaml +33 -0
  5. package/.github/workflows/release.yaml +112 -0
  6. package/.gitignore +41 -0
  7. package/.node-version +1 -0
  8. package/.npmignore +7 -0
  9. package/.vscode/extensions.json +10 -0
  10. package/.vscode/settings.json +44 -0
  11. package/LICENSE +21 -0
  12. package/README.md +160 -0
  13. package/commitlint.config.ts +3 -0
  14. package/cspell.json +27 -0
  15. package/dist/components.esm.js +2 -0
  16. package/dist/css/components.C24nnsjt.css +1 -0
  17. package/dist/hooks.esm.js +185 -0
  18. package/dist/index.esm.js +4 -0
  19. package/dist/js/components.nFDoAkCq.js +198 -0
  20. package/dist/js/utils.CVb1iSAU.js +330 -0
  21. package/dist/types/components.d.ts +1 -0
  22. package/dist/types/hooks.d.ts +1 -0
  23. package/dist/types/index.d.ts +1 -0
  24. package/dist/types/src/components/async-custom-show/index.d.ts +22 -0
  25. package/dist/types/src/components/carousel/hooks.d.ts +74 -0
  26. package/dist/types/src/components/carousel/index.d.ts +88 -0
  27. package/dist/types/src/components/custom-show/index.d.ts +21 -0
  28. package/dist/types/src/components/error-boundary/index.d.ts +31 -0
  29. package/dist/types/src/components/index.d.ts +8 -0
  30. package/dist/types/src/hooks/async-guard.d.ts +9 -0
  31. package/dist/types/src/hooks/index.d.ts +5 -0
  32. package/dist/types/src/hooks/observer.d.ts +70 -0
  33. package/dist/types/src/hooks/state.d.ts +44 -0
  34. package/dist/types/src/hooks/storage.d.ts +25 -0
  35. package/dist/types/src/hooks/timer.d.ts +16 -0
  36. package/dist/types/src/index.d.ts +3 -0
  37. package/dist/types/src/test/emiter-test.d.ts +1 -0
  38. package/dist/types/src/test/index.d.ts +1 -0
  39. package/dist/types/src/test/retry-async-test.d.ts +1 -0
  40. package/dist/types/src/utils/class-singleton.d.ts +6 -0
  41. package/dist/types/src/utils/concurrency.d.ts +17 -0
  42. package/dist/types/src/utils/debounce.d.ts +8 -0
  43. package/dist/types/src/utils/deep-copy.d.ts +11 -0
  44. package/dist/types/src/utils/dev.d.ts +8 -0
  45. package/dist/types/src/utils/download.d.ts +15 -0
  46. package/dist/types/src/utils/event-emitter/helpers.d.ts +36 -0
  47. package/dist/types/src/utils/event-emitter/index.d.ts +65 -0
  48. package/dist/types/src/utils/fetch-xhr/helpers.d.ts +28 -0
  49. package/dist/types/src/utils/fetch-xhr/index.d.ts +25 -0
  50. package/dist/types/src/utils/hash.d.ts +8 -0
  51. package/dist/types/src/utils/index.d.ts +15 -0
  52. package/dist/types/src/utils/is-deep-plain-equal.d.ts +15 -0
  53. package/dist/types/src/utils/json-convert.d.ts +66 -0
  54. package/dist/types/src/utils/micro-queue-scheduler.d.ts +14 -0
  55. package/dist/types/src/utils/raf-interval.d.ts +16 -0
  56. package/dist/types/src/utils/record-typed-map.d.ts +27 -0
  57. package/dist/types/src/utils/retry-async.d.ts +9 -0
  58. package/dist/types/src/utils/thenable.d.ts +15 -0
  59. package/dist/types/utils.d.ts +1 -0
  60. package/dist/utils.esm.js +2 -0
  61. package/eslint.config.ts +48 -0
  62. package/lint-staged.config.ts +13 -0
  63. package/oxfmt.config.ts +14 -0
  64. package/package.json +90 -0
  65. package/pnpm-workspace.yaml +3 -0
  66. package/scripts/sync-node-version/index.js +31 -0
  67. package/simple-git-hooks.js +4 -0
  68. package/src/components/async-custom-show/index.tsx +37 -0
  69. package/src/components/carousel/hooks.ts +312 -0
  70. package/src/components/carousel/index.module.scss +163 -0
  71. package/src/components/carousel/index.tsx +215 -0
  72. package/src/components/custom-show/index.tsx +31 -0
  73. package/src/components/error-boundary/index.tsx +48 -0
  74. package/src/components/index.ts +11 -0
  75. package/src/hooks/async-guard.ts +53 -0
  76. package/src/hooks/index.ts +5 -0
  77. package/src/hooks/observer.ts +236 -0
  78. package/src/hooks/state.ts +140 -0
  79. package/src/hooks/storage.ts +103 -0
  80. package/src/hooks/timer.ts +42 -0
  81. package/src/index.ts +3 -0
  82. package/src/test/emiter-test.ts +19 -0
  83. package/src/test/index.ts +35 -0
  84. package/src/test/retry-async-test.ts +8 -0
  85. package/src/utils/class-singleton.ts +23 -0
  86. package/src/utils/concurrency.ts +49 -0
  87. package/src/utils/debounce.ts +20 -0
  88. package/src/utils/deep-copy.ts +132 -0
  89. package/src/utils/dev.ts +16 -0
  90. package/src/utils/download.ts +39 -0
  91. package/src/utils/event-emitter/helpers.ts +80 -0
  92. package/src/utils/event-emitter/index.ts +171 -0
  93. package/src/utils/fetch-xhr/helpers.ts +85 -0
  94. package/src/utils/fetch-xhr/index.ts +103 -0
  95. package/src/utils/hash.ts +25 -0
  96. package/src/utils/index.ts +18 -0
  97. package/src/utils/is-deep-plain-equal.ts +45 -0
  98. package/src/utils/json-convert.ts +257 -0
  99. package/src/utils/micro-queue-scheduler.ts +38 -0
  100. package/src/utils/raf-interval.ts +42 -0
  101. package/src/utils/record-typed-map.ts +38 -0
  102. package/src/utils/retry-async.ts +30 -0
  103. package/src/utils/thenable.ts +30 -0
  104. package/tsconfig.json +43 -0
  105. package/types/scss.d.ts +10 -0
  106. package/vite.config.ts +51 -0
@@ -0,0 +1,140 @@
1
+ import { type SetStateAction, useState, useCallback, useRef } from 'react';
2
+
3
+ /**
4
+ * @author sonion
5
+ * @description 创建静态的state, 不会触发组件重新渲染
6
+ * @param {T} initialValue - 初始值
7
+ */
8
+ export function useStaticState<T>(
9
+ initialValue: T
10
+ ): [() => T, (t: T) => void, (t?: T) => T];
11
+ export function useStaticState<T>(
12
+ initialValue?: undefined
13
+ ): [() => T | undefined, (t: T) => void, (t?: T) => T | undefined];
14
+ export function useStaticState<T>(initialValue?: T) {
15
+ const ref = useRef<T>(initialValue as T);
16
+ const getValue = useCallback(() => ref.current, []);
17
+ const setValue = useCallback((t: T) => (ref.current = t), []);
18
+ const withValue = useCallback(
19
+ (t?: T) => (t === void 0 ? ref.current : (ref.current = t)),
20
+ []
21
+ );
22
+ return [getValue, setValue, withValue];
23
+ }
24
+
25
+ /**
26
+ * @author sonion
27
+ * @description 创建最新的回调函数,不触发重新执行,同时避免闭包问题。
28
+ * 作用类似19.2的useEffectEvent,但原理和用法不同。
29
+ * @param {T} dep - 依赖函数、依赖函数数组、依赖函数对象
30
+ * @returns {()=>T} - 获取最新回调函数、依赖函数数组、依赖函数对象的方法
31
+ */
32
+ export function useLatestCallback<T extends (...args: never) => unknown>(
33
+ dep: T
34
+ ): () => T;
35
+ export function useLatestCallback<T extends (...args: never) => unknown>(
36
+ dep?: T | undefined
37
+ ): () => T | undefined;
38
+ export function useLatestCallback(dep: (...args: unknown[]) => unknown) {
39
+ const ref = useRef(dep);
40
+ // eslint-disable-next-line react-hooks/refs
41
+ ref.current !== dep && (ref.current = dep);
42
+ return useCallback(() => ref.current, []);
43
+ }
44
+
45
+ /**
46
+ * @author sonion
47
+ * @description 创建相同值不触发组件重新渲染的state。和useCreateSafeRef类似,但useCreateSafeRef更专注于ref
48
+ * @param params - 参数对象
49
+ * @param {T} params.initialValue - 初始值
50
+ * @param {(val: T) => void} [params.onChange] - 变化回调
51
+ * @param {(prev: T, next: T) => boolean} [params.hasDiff] - 对比函数,默认对比引用是否不相同。
52
+ * @param {boolean} [params.onlyEvent] - 是否仅触发事件,不更新值,避免重渲染
53
+ */
54
+ export function useDistinctState<T>(params: {
55
+ initialValue: T | (() => T);
56
+ onChange?: (val: T) => void;
57
+ hasDiff?: (prev?: T, next?: T) => boolean;
58
+ onlyEvent: true;
59
+ }): [undefined, (val: SetStateAction<T>) => void, () => T];
60
+ export function useDistinctState<T>(params: {
61
+ initialValue: T | (() => T);
62
+ onChange?: (val: T) => void;
63
+ hasDiff?: (prev?: T, next?: T) => boolean;
64
+ onlyEvent?: false;
65
+ }): [T, (val: SetStateAction<T>) => void, () => T];
66
+ export function useDistinctState<T>({
67
+ initialValue,
68
+ onChange,
69
+ hasDiff = (prev, next) => prev !== next,
70
+ onlyEvent,
71
+ }: {
72
+ /** 初始值 */
73
+ initialValue: T;
74
+ /** 变化回调 */
75
+ onChange?: (val: T) => void;
76
+ /** 对比函数,默认对比引用是否不相同。 */
77
+ hasDiff?: (prev: T, next: T) => boolean;
78
+ /** 是否仅触发事件,不更新值,避免重渲染 */
79
+ onlyEvent?: true | false;
80
+ }) {
81
+ const prevRef = useRef<T>(void 0 as T);
82
+ // 初始化只可能是函数,所以包一层,在这层一起初始化prevRef的值,避免初始化重复调用
83
+ const initial = () =>
84
+ (prevRef.current =
85
+ typeof initialValue === 'function'
86
+ ? (initialValue as () => T)()
87
+ : initialValue);
88
+ const [value, setValue] = useState(initial);
89
+
90
+ const getOnChange = useLatestCallback(onChange);
91
+ const setValueDistinct = useCallback(
92
+ (val: SetStateAction<T>) => {
93
+ const value =
94
+ typeof val === 'function'
95
+ ? (val as (prevState: T) => T)(prevRef.current)
96
+ : val;
97
+ // eslint-disable-next-line react-hooks/exhaustive-deps
98
+ hasDiff ??= (prev, next) => prev !== next;
99
+ if (hasDiff(prevRef.current, value)) {
100
+ const onChange = getOnChange();
101
+ onChange?.(value);
102
+ prevRef.current = value;
103
+ onlyEvent || setValue(value); // 仅触发事件时,不更新值
104
+ }
105
+ },
106
+ [getOnChange, onlyEvent, setValue, !!hasDiff]
107
+ );
108
+ return [
109
+ onlyEvent ? void 0 : value, // 仅触发事件时,返回undefined
110
+ setValueDistinct,
111
+ useCallback(() => prevRef.current, []), // 获取上一次的值
112
+ ] as const;
113
+ }
114
+
115
+ /**
116
+ * @author sonion
117
+ * @description 创建安全的Ref引用
118
+ * @param {(oldNode?: T, newNode?: T) => boolean} [hasDiff] - 对比函数,默认对比引用是否不相同。
119
+ */
120
+ export const useCreateSafeRef = <T extends object = HTMLElement>(
121
+ hasDiff?: (oldNode?: T, newNode?: T) => boolean
122
+ ) => {
123
+ const [el, setEl] = useState<T>();
124
+ const isReadyRef = useRef(false); // 是否赋值完成
125
+ return [
126
+ el,
127
+ useCallback(
128
+ (node: T | null) => {
129
+ // eslint-disable-next-line react-hooks/exhaustive-deps
130
+ hasDiff ??= (oldNode, newNode) => oldNode !== newNode;
131
+ if (node && hasDiff(el, node)) {
132
+ isReadyRef.current = true;
133
+ setEl(node);
134
+ }
135
+ },
136
+ [el, !!hasDiff] // 对比函数是否存在,对比函数又要稳定函数引用
137
+ ),
138
+ isReadyRef,
139
+ ] as const;
140
+ };
@@ -0,0 +1,103 @@
1
+ import { useCallback, useEffect } from 'react';
2
+ import { useDistinctState } from './state';
3
+
4
+ type StorageParams<T> = Omit<
5
+ Parameters<typeof useDistinctState<T>>[0],
6
+ 'onlyEvent'
7
+ > & {
8
+ /** 储存的key */
9
+ key: string;
10
+ /** 储存类型。 localStorage 或 sessionStorage */
11
+ storage?: typeof localStorage | typeof sessionStorage;
12
+ /** 初始化类型检查函数,检查不通过使用初始值。可避免类型不对引起的错误 */
13
+ checkType?: (val: T) => boolean;
14
+ /** tab关闭前的回调, 相同key的不同回调只有初始生效。 */
15
+ beforeunload?: () => void;
16
+ };
17
+
18
+ /**
19
+ * @author sonion
20
+ * @description 本地储存
21
+ * @param params - 参数对象
22
+ * @param params.key - 储存的key
23
+ * @param params.initialValue - 初始值
24
+ * @param [params.hasDiff] - 对比函数,默认对比引用是否不相同。
25
+ * @param params.onChange - 变化回调
26
+ * @param [params.storage] - 储存类型。 localStorage 或 sessionStorage
27
+ * @param [params.checkType] - 初始化类型检查函数,检查不通过使用初始值。可避免类型不对引起的错误
28
+ * @param [params.beforeunload] - tab关闭前的回调, 相同key的不同回调只有初始生效。
29
+ */
30
+ export const useStorage = <T>({
31
+ key,
32
+ initialValue,
33
+ hasDiff,
34
+ onChange,
35
+ storage = localStorage,
36
+ checkType = () => true,
37
+ beforeunload,
38
+ }: StorageParams<T>) => {
39
+ const [storedValue, setStoredValue] = useDistinctState<T>({
40
+ initialValue: () => {
41
+ try {
42
+ const saved = storage.getItem(key);
43
+ const saved2 = saved ? JSON.parse(saved) : initialValue;
44
+ return checkType(saved2) ? saved2 : initialValue;
45
+ } catch (err) {
46
+ console.warn('初始化出错,已使用默认值', err);
47
+ return initialValue;
48
+ }
49
+ },
50
+ hasDiff,
51
+ onChange,
52
+ });
53
+
54
+ const setValue = useCallback(
55
+ (value: T | ((val: T) => T)) => {
56
+ try {
57
+ setStoredValue((old) => {
58
+ const valueToStore = value instanceof Function ? value(old) : value;
59
+ const newValue = JSON.stringify(valueToStore);
60
+ storage.setItem(key, newValue);
61
+ window.dispatchEvent(
62
+ new StorageEvent('storage', {
63
+ key,
64
+ newValue,
65
+ storageArea: storage,
66
+ })
67
+ );
68
+ return valueToStore;
69
+ });
70
+ } catch (error) {
71
+ console.error('持久化储存错误', error);
72
+ }
73
+ },
74
+ [key, storage, setStoredValue]
75
+ );
76
+
77
+ useEffect(() => {
78
+ const handleStorageChange = (event: StorageEvent) => {
79
+ if (event.storageArea !== storage || event.key !== key) {
80
+ return;
81
+ }
82
+ try {
83
+ const newValue = event.newValue
84
+ ? JSON.parse(event.newValue)
85
+ : initialValue;
86
+ setStoredValue(newValue);
87
+ } catch (error) {
88
+ console.error('storage事件处理出错', error);
89
+ }
90
+ };
91
+ window.addEventListener('storage', handleStorageChange);
92
+ return () => window.removeEventListener('storage', handleStorageChange);
93
+ }, [key, storage, initialValue, setStoredValue]);
94
+
95
+ useEffect(() => {
96
+ if (!beforeunload) return;
97
+ window.addEventListener('beforeunload', beforeunload);
98
+ // 不用返回清理,因为组件卸载事件不移除
99
+ // eslint-disable-next-line react-hooks/exhaustive-deps
100
+ }, []);
101
+
102
+ return [storedValue, setValue] as const;
103
+ };
@@ -0,0 +1,42 @@
1
+ import { useRef, useCallback } from 'react';
2
+ import { type RAfIntervalReturn, rAfInterval, clearRAfInterval } from '@/utils';
3
+
4
+ /**
5
+ * @author sonion
6
+ * @description 自管理定时器的interval
7
+ * @param {()=>void} cb - 回调函数
8
+ * @param {number} duration - 时间间隔
9
+ * @returns {[() => void, () => void]} - [启动函数, 清除函数]
10
+ */
11
+ export const useInterval = (cb: () => void, duration: number) => {
12
+ const timer = useRef<ReturnType<typeof setTimeout>>(void 0);
13
+ const cbRef = useRef(cb);
14
+ cbRef.current = cb;
15
+ const run = useCallback(() => {
16
+ clearTimeout(timer.current);
17
+ timer.current = setTimeout(() => {
18
+ cbRef.current();
19
+ // eslint-disable-next-line react-hooks/immutability
20
+ run?.();
21
+ }, duration);
22
+ }, [duration]);
23
+ const stop = useCallback(() => clearTimeout(timer.current), [timer]);
24
+ return [run, stop];
25
+ };
26
+
27
+ /**
28
+ * @author sonion
29
+ * @description 自管理定时器的rAfInterval
30
+ * @param {()=>void} cb - 回调函数
31
+ * @param {number} duration - 时间间隔
32
+ * @returns {[() => void, () => void]} - [启动函数, 清除函数]
33
+ */
34
+ export const useRAfInterval = (cb: () => void, duration: number) => {
35
+ const timer = useRef<RAfIntervalReturn>(void 0);
36
+ const run = useCallback(() => {
37
+ clearRAfInterval(timer.current);
38
+ timer.current = rAfInterval(cb, duration);
39
+ }, [cb, duration]);
40
+ const stop = useCallback(() => clearRAfInterval(timer.current), [timer]);
41
+ return [run, stop] as const;
42
+ };
package/src/index.ts ADDED
@@ -0,0 +1,3 @@
1
+ export * from './components';
2
+ export * from './hooks';
3
+ export * from './utils';
@@ -0,0 +1,19 @@
1
+ import { EventEmitter } from '../utils/index.ts';
2
+
3
+ const emitter = new EventEmitter<{
4
+ test: 'fgh';
5
+ onTest?: Record<string, unknown>;
6
+ optionalTest: string;
7
+ }>();
8
+
9
+ emitter.addEventListener('test', (data) => {
10
+ console.log('test', data);
11
+ });
12
+
13
+ emitter.addEventListener('onTest', (data) => {
14
+ console.log('onTest', data);
15
+ });
16
+
17
+ emitter.emit('onTest');
18
+
19
+ emitter.emit('optionalTest', '123');
@@ -0,0 +1,35 @@
1
+ import {
2
+ convertCamel2Pascal,
3
+ convertPascal2Camel,
4
+ convertCamel2Snake,
5
+ convertSnake2Camel,
6
+ } from '../utils';
7
+
8
+ const testObj = {
9
+ camelCaseKey: 'value',
10
+ PascalCaseKey: 'value',
11
+ snake_case_key: 'value',
12
+ nestedObj: {
13
+ camelCaseKey2: 'value',
14
+ PascalCaseKe5y2: 'value',
15
+ snake_ca6se_key2: 'value',
16
+ nextArray: [
17
+ {
18
+ camelCaseKey: 'value',
19
+ PascalCaseKey: 'value',
20
+ snake_case_key: 'value',
21
+ },
22
+ ],
23
+ },
24
+ };
25
+
26
+ const testObjSnake = convertCamel2Snake([testObj, testObj.nestedObj]);
27
+ const ignoreKeys = ['pascal_case_ke5y2', 'deep_key'] as const;
28
+ const testObjCamel = convertSnake2Camel(testObjSnake, ignoreKeys);
29
+ const testObjPascal = convertCamel2Pascal(testObj);
30
+ const testObjCamel2 = convertPascal2Camel(testObjPascal);
31
+
32
+ console.clog('testObjSnake:', testObjSnake);
33
+ console.clog('testObjCamel:', testObjCamel);
34
+ console.clog('testObjPascal:', testObjPascal);
35
+ console.clog('testObjCamel2:', testObjCamel2);
@@ -0,0 +1,8 @@
1
+ import { retryAsync } from '../utils/retry-async.ts';
2
+
3
+ retryAsync(() => {
4
+ console.log('retryAsync');
5
+ return Promise.reject('success');
6
+ }).catch((err) => {
7
+ console.log('最后', err);
8
+ });
@@ -0,0 +1,23 @@
1
+ /**
2
+ * @author sonion
3
+ * @description 构造函数转单例工具
4
+ * @param className - 要转单例的构造函数
5
+ */
6
+ export const singleton = <
7
+ T extends new (...args: ConstructorParameters<T>) => R,
8
+ R extends object = InstanceType<T>,
9
+ >(
10
+ className: T
11
+ ): T => {
12
+ // 单例模式,生成函数
13
+ let ins: R;
14
+ const proxy = new Proxy<T>(className, {
15
+ construct(target: T, args: ConstructorParameters<T>): R {
16
+ if (!ins) ins = new target(...args);
17
+ else console.warn(`${className.name}为单例构造函数,只能有一个实例`);
18
+ return ins;
19
+ },
20
+ });
21
+ className.prototype.constructor = proxy;
22
+ return proxy;
23
+ };
@@ -0,0 +1,49 @@
1
+ import { promiseTry } from './thenable';
2
+
3
+ /**
4
+ * @author sonion
5
+ * @description 并发控制器
6
+ * @param {number} concurrency - 并发数
7
+ */
8
+ export class ConcurrencyController<T> {
9
+ /** 任务队列 */
10
+ private queue: Array<{
11
+ task: () => Promise<T>;
12
+ resolve: (value: T | PromiseLike<T>) => void;
13
+ reject: (reason?: unknown) => void;
14
+ }> = [];
15
+ /** 并发数 */
16
+ private concurrency: number;
17
+ /** 当前运行中的任务数 */
18
+ private running = 0;
19
+
20
+ constructor(concurrency = 5) {
21
+ this.concurrency = concurrency;
22
+ }
23
+
24
+ push(task: () => Promise<T>) {
25
+ return new Promise<T>((resolve, reject) => {
26
+ this.queue.push({ task, resolve, reject });
27
+ this.run();
28
+ });
29
+ }
30
+
31
+ run() {
32
+ while (this.running < this.concurrency && this.queue.length > 0) {
33
+ this.next();
34
+ }
35
+ }
36
+
37
+ private next() {
38
+ if (this.running >= this.concurrency) return;
39
+ const { task, resolve, reject } = this.queue.shift() ?? {};
40
+ if (!task) return;
41
+ this.running++;
42
+ promiseTry(task)
43
+ .then(resolve, reject)
44
+ .finally(() => {
45
+ this.running--;
46
+ this.run();
47
+ });
48
+ }
49
+ }
@@ -0,0 +1,20 @@
1
+ /**
2
+ * @author sonion
3
+ * @description 防抖
4
+ * @param {(...args: unknown[])=>void} fn - 防抖的函数
5
+ * @param {number} delay - 毫秒
6
+ * @returns {(...args: unknown[])=>void} - 防抖后的函数
7
+ */
8
+ export const debounce = <A extends unknown[], R, T = unknown>(
9
+ fn: (this: T, ...args: A) => R,
10
+ delay: number
11
+ ): ((this: T, ...args: A) => void) => {
12
+ let timer: ReturnType<typeof setTimeout>;
13
+
14
+ return function (...args: A): void {
15
+ timer && clearTimeout(timer);
16
+ timer = setTimeout(() => {
17
+ fn.apply(this as unknown as ThisParameterType<typeof fn>, args);
18
+ }, delay);
19
+ };
20
+ };
@@ -0,0 +1,132 @@
1
+ /**
2
+ * @author sonion
3
+ * @description 获取元素的数据类型
4
+ * @param {unknown} data - 要判断的参数
5
+ * @returns {string} - 元素的数据类型
6
+ */
7
+ const getObjectType = (() => {
8
+ const regexp = /^\[[a-z]+ ([A-Za-z]+)\]$/;
9
+ return (data: object) => {
10
+ return (
11
+ (data.constructor && data.constructor.name) ||
12
+ Object.prototype.toString.call(data).replace(regexp, '$1')
13
+ );
14
+ };
15
+ })();
16
+
17
+ /**
18
+ * @author sonion
19
+ * @description 判断是否为对象
20
+ * @param {unknown} val - 要判断的参数
21
+ * @returns {boolean} - 是否为对象
22
+ */
23
+ const isObject = <T extends object>(val: unknown): val is T =>
24
+ val === Object(val);
25
+
26
+ /**
27
+ * @author sonion
28
+ * @description 判断是否为Set
29
+ * @param {unknown} val - 要判断的参数
30
+ * @returns {boolean} - 是否为Set
31
+ */
32
+ const isSet = <T>(val: unknown): val is Set<T> => {
33
+ if (!isObject(val)) return false;
34
+ if (val instanceof Set) return true;
35
+ return (
36
+ Object.prototype.hasOwnProperty.call(val as object, 'size') &&
37
+ typeof (val as Set<T>).add === 'function' &&
38
+ typeof (val as Set<T>).has === 'function'
39
+ );
40
+ };
41
+
42
+ /**
43
+ * @author sonion
44
+ * @description 判断是否为Map
45
+ * @param {unknown} val - 要判断的参数
46
+ * @returns {boolean} - 是否为Map
47
+ */
48
+ const isMap = <K, V>(val: unknown): val is Map<K, V> => {
49
+ if (!isObject(val)) return false;
50
+ if (val instanceof Map) return true;
51
+ return (
52
+ Object.prototype.hasOwnProperty.call(val as object, 'size') &&
53
+ typeof (val as Map<K, V>).set === 'function' &&
54
+ typeof (val as Map<K, V>).get === 'function'
55
+ );
56
+ };
57
+
58
+ // 深拷贝参数和返回类型
59
+ type DeepCloneResult = Record<string | symbol, unknown>;
60
+ type DeepCloneParamReturn = DeepCloneResult | Array<unknown>;
61
+ type ArrayElement<T> = T extends Array<infer U> ? U : never;
62
+ type SetElement<T> = T extends Set<infer U> ? U : never;
63
+ type MapElement<T> = T extends Map<infer K, infer V> ? [K, V] : never;
64
+
65
+ /**
66
+ * @author sonion
67
+ * @description 根据参数类型创建一个新的对象或集合或映射
68
+ * @param {T} value - 要创建的参数
69
+ */
70
+ const createResult = <T>(value: T) => {
71
+ if (Array.isArray(value)) return [] as ArrayElement<T>[];
72
+ if (isSet(value)) return new Set<SetElement<T>>();
73
+ if (isMap(value)) return new Map<MapElement<T>[0], MapElement<T>[1]>();
74
+ return {} as T;
75
+ };
76
+
77
+ /**
78
+ * @author sonion
79
+ * @description 解决了循环引用、原型一致的深度克隆 // 原生structuredClone 没解决Symbol、原型链,兼容到22年8月
80
+ * @param {DeepCloneParamReturn} value 要克隆的对象
81
+ * @param {boolean} [isCopyProto] 是否拷贝原型链 -默认值:true
82
+ * @returns {DeepCloneParamReturn} - 克隆后的对象
83
+ */
84
+ export const deepClone = <
85
+ T extends DeepCloneParamReturn,
86
+ U extends DeepCloneParamReturn,
87
+ >(
88
+ value: T,
89
+ isCopyProto = true
90
+ ): U => {
91
+ const cache = new WeakMap(); // 解决循环引用 weakMap不影响垃圾回收
92
+ const _deepClone = <T>(value: T) => {
93
+ if (!isObject(value)) return value;
94
+ switch (getObjectType(value)) {
95
+ case 'Date':
96
+ case 'RegExp':
97
+ case 'Function': {
98
+ const constructor = value.constructor as new (
99
+ ...args: unknown[]
100
+ ) => unknown;
101
+ return new constructor(value); // 用其构造函数传入 原数据或value.valueOf() 就能创建一个新的对象
102
+ }
103
+ case 'WeakMap':
104
+ case 'WeakSet':
105
+ return value;
106
+ }
107
+ if (cache.get(value)) return cache.get(value); // 解决循环引用
108
+ const result = createResult(value);
109
+ isCopyProto && Object.setPrototypeOf(result, Object.getPrototypeOf(value)); // 拷贝原型链
110
+ cache.set(value, result); // 解决循环引用 把克隆过的保存。因为是递归,必须提前报存。否则后面的循环引用可能获取不到,一直进入下一层
111
+ if (isSet(value)) {
112
+ for (const v of value) {
113
+ (result as Set<T>).add(_deepClone(v));
114
+ }
115
+ return result;
116
+ }
117
+ if (isMap(value)) {
118
+ for (const [k, v] of value) {
119
+ (result as Map<T, T>).set(_deepClone(k), _deepClone(v)); // Map key可以是对象,所以也要深拷贝
120
+ }
121
+ return result;
122
+ }
123
+ Reflect.ownKeys(value).map(
124
+ (key) =>
125
+ ((result as DeepCloneResult)[key] = _deepClone(
126
+ (value as DeepCloneResult)[key]
127
+ ))
128
+ );
129
+ return result;
130
+ };
131
+ return _deepClone(value);
132
+ };
@@ -0,0 +1,16 @@
1
+ declare global {
2
+ interface Console {
3
+ clog: typeof console.log;
4
+ }
5
+ }
6
+
7
+ type ClogParams = Parameters<typeof console.log>;
8
+
9
+ export const clog = (...args: ClogParams) => {
10
+ const style =
11
+ 'padding: 4px 8px; border-radius: 4px; color:#fff; background: linear-gradient(90deg, #833ab4 0%, #fd1d1d 50%, #fcb045 100%);';
12
+ const first = typeof args[0] === 'string' ? args.shift() : '==>';
13
+ args.unshift(`%c ${first}`, style);
14
+ console.log(...args);
15
+ };
16
+ console.clog = clog;
@@ -0,0 +1,39 @@
1
+ /**
2
+ * @author sonion
3
+ * @description 浏览器原生下载。需要content-disposition: attachment; filename=xxx响应头支持
4
+ * a元素、window.open;target为_self短时间打开多个,后面的会覆盖前面的。为_blank时都会拦截,但window.open可以通过返回值检测是否被拦截
5
+ * @param {string} url - 下载链接
6
+ */
7
+ export const browserNativeDownload = (url: string) =>
8
+ new Promise<boolean>((resolve, reject) => {
9
+ try {
10
+ const newTab = window.open(url, '_blank');
11
+ if (!newTab || newTab.closed || typeof newTab.closed === 'undefined') {
12
+ reject(new Error('Tab 可能被拦截了'));
13
+ } else {
14
+ resolve(true);
15
+ }
16
+ } catch {
17
+ reject(new Error('下载出错'));
18
+ }
19
+ });
20
+
21
+ /**
22
+ * @author sonion
23
+ * @description blob下载
24
+ * @param {Blob} blob - 下载的blob数据
25
+ * @param {string} fileName - 下载的文件名
26
+ * @param {boolean} [inline] - 是否内联下载(不开新tab) 默认内联
27
+ */
28
+ export const blobDownload = (blob: Blob, fileName: string, inline = true) => {
29
+ const url = URL.createObjectURL(blob);
30
+ try {
31
+ const a = document.createElement('a');
32
+ a.href = url;
33
+ a.download = fileName;
34
+ inline || a.setAttribute('target', '_blank');
35
+ a.click();
36
+ } finally {
37
+ URL.revokeObjectURL(url);
38
+ }
39
+ };