@wwog/react 1.3.12 → 1.3.14
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.
- package/README.md +91 -3
- package/dist/index.d.mts +116 -19
- package/dist/index.js +1 -1
- package/package.json +1 -1
- package/src/algorithm/date/zellersKongruenz.ts +9 -14
- package/src/algorithm/date.ts +2 -2
- package/src/components/Layout/index.ts +2 -2
- package/src/components/ProcessControl/If.tsx +2 -2
- package/src/components/Sundry/Boundary.tsx +67 -0
- package/src/components/Sundry/Portal.tsx +59 -0
- package/src/components/Sundry/Repeat.tsx +34 -0
- package/src/components/Sundry/index.ts +8 -5
- package/src/hooks/index.ts +1 -1
- package/src/hooks/useScreen.ts +54 -65
- package/src/index.ts +5 -5
- package/src/utils/constants.ts +7 -16
- package/src/utils/createExternalState.test.tsx +36 -17
- package/src/utils/createExternalState.ts +93 -95
- package/src/utils/index.ts +7 -7
- package/src/utils/promise.ts +18 -18
- package/src/utils/ruleChecker.test.ts +336 -349
- package/src/utils/ruleChecker.ts +98 -181
- package/src/utils/sundry.ts +66 -67
package/src/hooks/useScreen.ts
CHANGED
|
@@ -1,99 +1,88 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
breakpoints,
|
|
4
|
-
DefBreakpointDesc,
|
|
5
|
-
type BreakpointDesc,
|
|
6
|
-
type BreakpointName,
|
|
7
|
-
} from "../utils";
|
|
1
|
+
import {useEffect, useMemo, useRef, useState} from 'react'
|
|
2
|
+
import {type BreakpointDesc, type BreakpointName, DefBreakpointDesc, breakpoints} from '../utils'
|
|
8
3
|
|
|
9
|
-
const reverseBreakpoints = [...breakpoints].reverse()
|
|
4
|
+
const reverseBreakpoints = [...breakpoints].reverse()
|
|
5
|
+
|
|
6
|
+
const isBrowser = typeof window !== 'undefined'
|
|
10
7
|
|
|
11
8
|
export function getCurrentBreakpoint(
|
|
12
9
|
breakpointDesc: BreakpointDesc,
|
|
13
|
-
width: number
|
|
10
|
+
width: number,
|
|
14
11
|
): BreakpointName {
|
|
15
12
|
for (const bp of reverseBreakpoints) {
|
|
16
|
-
const bpValue = breakpointDesc[bp]
|
|
13
|
+
const bpValue = breakpointDesc[bp]
|
|
17
14
|
if (bpValue !== undefined && !Number.isNaN(bpValue) && width >= bpValue) {
|
|
18
|
-
return bp
|
|
15
|
+
return bp
|
|
19
16
|
}
|
|
20
17
|
}
|
|
21
|
-
return
|
|
18
|
+
return 'base'
|
|
22
19
|
}
|
|
23
20
|
|
|
24
21
|
export function useScreen(breakpointDesc: BreakpointDesc = DefBreakpointDesc) {
|
|
22
|
+
// 用 JSON 序列化稳定化对象引用,避免调用方每次传字面量对象导致 effect 无限重跑
|
|
23
|
+
const stableKey = useMemo(() => JSON.stringify(breakpointDesc), [breakpointDesc])
|
|
24
|
+
const descRef = useRef(breakpointDesc)
|
|
25
|
+
descRef.current = breakpointDesc
|
|
26
|
+
|
|
25
27
|
const [currentBreakpoint, setCurrentBreakpoint] = useState<BreakpointName>(
|
|
26
|
-
|
|
27
|
-
|
|
28
|
+
// lazy initializer:SSR 环境下 window 不存在,fallback 到 "base"
|
|
29
|
+
() => (isBrowser ? getCurrentBreakpoint(breakpointDesc, window.innerWidth) : 'base'),
|
|
30
|
+
)
|
|
28
31
|
|
|
29
32
|
useEffect(() => {
|
|
30
|
-
|
|
31
|
-
|
|
33
|
+
if (!isBrowser) return
|
|
34
|
+
|
|
35
|
+
let mediaQueries: MediaQueryList[] = []
|
|
36
|
+
let listeners: (() => void)[] = []
|
|
32
37
|
|
|
33
38
|
const updateMediaQueries = () => {
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
listeners = [];
|
|
39
|
+
listeners.forEach((removeListener) => removeListener())
|
|
40
|
+
mediaQueries = []
|
|
41
|
+
listeners = []
|
|
38
42
|
|
|
39
|
-
|
|
40
|
-
const currentBp = getCurrentBreakpoint(
|
|
41
|
-
setCurrentBreakpoint(currentBp)
|
|
43
|
+
const desc = descRef.current
|
|
44
|
+
const currentBp = getCurrentBreakpoint(desc, window.innerWidth)
|
|
45
|
+
setCurrentBreakpoint(currentBp)
|
|
42
46
|
|
|
43
|
-
|
|
44
|
-
const currentIndex = breakpoints.indexOf(currentBp);
|
|
47
|
+
const currentIndex = breakpoints.indexOf(currentBp)
|
|
45
48
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
const nextMinWidth = breakpointDesc[nextBp];
|
|
49
|
+
const nextBp = breakpoints[currentIndex + 1]
|
|
50
|
+
if (nextBp && desc[nextBp] !== undefined) {
|
|
51
|
+
const nextMinWidth = desc[nextBp]
|
|
50
52
|
if (!Number.isNaN(nextMinWidth)) {
|
|
51
|
-
const mediaQuery = window.matchMedia(
|
|
52
|
-
|
|
53
|
-
)
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
mediaQuery.addEventListener("change", handleMediaChange);
|
|
57
|
-
listeners.push(() =>
|
|
58
|
-
mediaQuery.removeEventListener("change", handleMediaChange)
|
|
59
|
-
);
|
|
53
|
+
const mediaQuery = window.matchMedia(`(min-width: ${nextMinWidth}px)`)
|
|
54
|
+
mediaQueries.push(mediaQuery)
|
|
55
|
+
const handleMediaChange = () => updateMediaQueries()
|
|
56
|
+
mediaQuery.addEventListener('change', handleMediaChange)
|
|
57
|
+
listeners.push(() => mediaQuery.removeEventListener('change', handleMediaChange))
|
|
60
58
|
} else {
|
|
61
|
-
throw new Error(
|
|
62
|
-
`Invalid breakpoint value for ${nextBp}: ${breakpointDesc[nextBp]}`
|
|
63
|
-
);
|
|
59
|
+
throw new Error(`Invalid breakpoint value for ${nextBp}: ${desc[nextBp]}`)
|
|
64
60
|
}
|
|
65
61
|
}
|
|
66
62
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
const prevMinWidth = breakpointDesc[prevBp];
|
|
63
|
+
const prevBp = breakpoints[currentIndex - 1]
|
|
64
|
+
if (prevBp && desc[prevBp] !== undefined) {
|
|
65
|
+
const prevMinWidth = desc[prevBp]
|
|
71
66
|
if (!Number.isNaN(prevMinWidth)) {
|
|
72
|
-
const mediaQuery = window.matchMedia(
|
|
73
|
-
|
|
74
|
-
)
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
mediaQuery.addEventListener("change", handleMediaChange);
|
|
78
|
-
listeners.push(() =>
|
|
79
|
-
mediaQuery.removeEventListener("change", handleMediaChange)
|
|
80
|
-
);
|
|
67
|
+
const mediaQuery = window.matchMedia(`(max-width: ${prevMinWidth - 1}px)`)
|
|
68
|
+
mediaQueries.push(mediaQuery)
|
|
69
|
+
const handleMediaChange = () => updateMediaQueries()
|
|
70
|
+
mediaQuery.addEventListener('change', handleMediaChange)
|
|
71
|
+
listeners.push(() => mediaQuery.removeEventListener('change', handleMediaChange))
|
|
81
72
|
} else {
|
|
82
|
-
throw new Error(
|
|
83
|
-
`Invalid breakpoint value for ${prevBp}: ${breakpointDesc[prevBp]}`
|
|
84
|
-
);
|
|
73
|
+
throw new Error(`Invalid breakpoint value for ${prevBp}: ${desc[prevBp]}`)
|
|
85
74
|
}
|
|
86
75
|
}
|
|
87
|
-
}
|
|
76
|
+
}
|
|
88
77
|
|
|
89
|
-
|
|
90
|
-
updateMediaQueries();
|
|
78
|
+
updateMediaQueries()
|
|
91
79
|
|
|
92
|
-
// 清理函数
|
|
93
80
|
return () => {
|
|
94
|
-
listeners.forEach((removeListener) => removeListener())
|
|
95
|
-
}
|
|
96
|
-
|
|
81
|
+
listeners.forEach((removeListener) => removeListener())
|
|
82
|
+
}
|
|
83
|
+
// stableKey 作为稳定化的依赖,替代直接依赖 breakpointDesc 对象引用
|
|
84
|
+
// biome-ignore lint/correctness/useExhaustiveDependencies: stableKey is the stable proxy for breakpointDesc object identity
|
|
85
|
+
}, [stableKey])
|
|
97
86
|
|
|
98
|
-
return currentBreakpoint
|
|
87
|
+
return currentBreakpoint
|
|
99
88
|
}
|
package/src/index.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
export * from
|
|
2
|
-
export * from
|
|
3
|
-
export * from
|
|
1
|
+
export * from './components/ProcessControl'
|
|
2
|
+
export * from './components/Sundry'
|
|
3
|
+
export * from './components/Struct'
|
|
4
4
|
|
|
5
|
-
export * from
|
|
5
|
+
export * from './hooks'
|
|
6
6
|
|
|
7
|
-
export * from
|
|
7
|
+
export * from './utils'
|
package/src/utils/constants.ts
CHANGED
|
@@ -1,13 +1,4 @@
|
|
|
1
|
-
export const breakpoints = [
|
|
2
|
-
"base",
|
|
3
|
-
"xs",
|
|
4
|
-
"sm",
|
|
5
|
-
"md",
|
|
6
|
-
"lg",
|
|
7
|
-
"xl",
|
|
8
|
-
"2xl",
|
|
9
|
-
"3xl",
|
|
10
|
-
] as const;
|
|
1
|
+
export const breakpoints = ['base', 'xs', 'sm', 'md', 'lg', 'xl', '2xl', '3xl'] as const
|
|
11
2
|
|
|
12
3
|
export const DefBreakpointDesc: BreakpointDesc = {
|
|
13
4
|
xs: 475,
|
|
@@ -15,11 +6,11 @@ export const DefBreakpointDesc: BreakpointDesc = {
|
|
|
15
6
|
md: 768,
|
|
16
7
|
lg: 1024,
|
|
17
8
|
xl: 1280,
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
}
|
|
21
|
-
export type BreakpointName = (typeof breakpoints)[number]
|
|
9
|
+
'2xl': 1536,
|
|
10
|
+
'3xl': 1920,
|
|
11
|
+
}
|
|
12
|
+
export type BreakpointName = (typeof breakpoints)[number]
|
|
22
13
|
|
|
23
|
-
export type BreakpointDesc = Partial<Record<BreakpointName, number
|
|
14
|
+
export type BreakpointDesc = Partial<Record<BreakpointName, number>>
|
|
24
15
|
|
|
25
|
-
export type Responsive<T> = T | Partial<Record<BreakpointName, T
|
|
16
|
+
export type Responsive<T> = T | Partial<Record<BreakpointName, T>>
|
|
@@ -120,30 +120,49 @@ describe("createExternalState", () => {
|
|
|
120
120
|
expect(state.__listeners.length).toBe(0);
|
|
121
121
|
});
|
|
122
122
|
|
|
123
|
-
it("
|
|
124
|
-
const
|
|
123
|
+
it("测试 onSet 每次 set 都会触发", () => {
|
|
124
|
+
const mockOnSet = vi.fn((...args) => void 0);
|
|
125
125
|
const initialState: string = "initial";
|
|
126
126
|
const state = createExternalState(initialState, {
|
|
127
|
-
|
|
127
|
+
onSet: mockOnSet,
|
|
128
128
|
});
|
|
129
129
|
state.set("updated");
|
|
130
|
-
expect(
|
|
131
|
-
expect(
|
|
130
|
+
expect(mockOnSet).toHaveBeenCalledTimes(1);
|
|
131
|
+
expect(mockOnSet).toHaveBeenCalledWith("updated", initialState);
|
|
132
|
+
state.set("updated");
|
|
133
|
+
expect(mockOnSet).toHaveBeenCalledTimes(2);
|
|
134
|
+
expect(mockOnSet).toHaveBeenCalledWith("updated", "updated");
|
|
135
|
+
state.set("updated2");
|
|
136
|
+
expect(mockOnSet).toHaveBeenCalledTimes(3);
|
|
137
|
+
expect(mockOnSet).toHaveBeenCalledWith("updated2", "updated");
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
it("测试 onChange 仅在值变化时触发", () => {
|
|
141
|
+
const mockOnChange = vi.fn((...args) => void 0);
|
|
142
|
+
const initialState: string = "initial";
|
|
143
|
+
const state = createExternalState(initialState, {
|
|
144
|
+
onChange: mockOnChange,
|
|
145
|
+
});
|
|
146
|
+
state.set("updated");
|
|
147
|
+
expect(mockOnChange).toHaveBeenCalledTimes(1);
|
|
148
|
+
expect(mockOnChange).toHaveBeenCalledWith("updated", initialState);
|
|
149
|
+
state.set("updated");
|
|
150
|
+
expect(mockOnChange).toHaveBeenCalledTimes(1);
|
|
132
151
|
state.set("updated2");
|
|
133
|
-
expect(
|
|
134
|
-
expect(
|
|
152
|
+
expect(mockOnChange).toHaveBeenCalledTimes(2);
|
|
153
|
+
expect(mockOnChange).toHaveBeenCalledWith("updated2", "updated");
|
|
135
154
|
});
|
|
136
155
|
|
|
137
|
-
it("
|
|
138
|
-
const
|
|
156
|
+
it("测试异步 onSet 回调", async () => {
|
|
157
|
+
const mockAsyncOnSet = vi.fn().mockResolvedValue(undefined);
|
|
139
158
|
const initialState: string = "initial";
|
|
140
159
|
const state = createExternalState(initialState, {
|
|
141
|
-
|
|
160
|
+
onSet: mockAsyncOnSet,
|
|
142
161
|
});
|
|
143
162
|
|
|
144
163
|
state.set("updated");
|
|
145
|
-
expect(
|
|
146
|
-
expect(
|
|
164
|
+
expect(mockAsyncOnSet).toHaveBeenCalledTimes(1);
|
|
165
|
+
expect(mockAsyncOnSet).toHaveBeenCalledWith("updated", initialState);
|
|
147
166
|
});
|
|
148
167
|
|
|
149
168
|
it("测试复杂数据类型", async () => {
|
|
@@ -321,17 +340,17 @@ describe("createStorageState", () => {
|
|
|
321
340
|
consoleSpy.mockRestore();
|
|
322
341
|
});
|
|
323
342
|
|
|
324
|
-
it("
|
|
325
|
-
const
|
|
343
|
+
it("测试存储 onSet 回调", () => {
|
|
344
|
+
const mockOnSet = vi.fn();
|
|
326
345
|
const state = createStorageState("test-key", "initial" as string, {
|
|
327
346
|
storageType: "local",
|
|
328
|
-
|
|
347
|
+
onSet: mockOnSet,
|
|
329
348
|
});
|
|
330
349
|
|
|
331
350
|
state.set("updated");
|
|
332
351
|
|
|
333
|
-
expect(
|
|
334
|
-
expect(
|
|
352
|
+
expect(mockOnSet).toHaveBeenCalledTimes(1);
|
|
353
|
+
expect(mockOnSet).toHaveBeenCalledWith("updated", "initial");
|
|
335
354
|
expect(localStorage.getItem("test-key")).toBe('"updated"');
|
|
336
355
|
});
|
|
337
356
|
|
|
@@ -1,24 +1,14 @@
|
|
|
1
|
-
import
|
|
2
|
-
import {
|
|
1
|
+
import {useSyncExternalStore} from 'react'
|
|
2
|
+
import {safePromiseTry} from './promise'
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
|
-
* @
|
|
6
|
-
* @
|
|
7
|
-
* @template T The type of the state / 状态的类型
|
|
8
|
-
*/
|
|
9
|
-
export type CreateStateListener<T> = (state: T) => void;
|
|
10
|
-
|
|
11
|
-
/**
|
|
12
|
-
* @zh 如果需要在变更状态时执行副作用,可以传入函数,对于异步函数,会在更改状态后执行,不会阻塞状态更新, 尽可能在外部使用useEffect处理异步副作用
|
|
13
|
-
* @en If you need to perform side effects when changing the state, you can pass a function. For asynchronous functions, it will be executed after the state changes without blocking the state update, so it's best to use useEffect for handling asynchronous side effects.
|
|
5
|
+
* @zh 状态回调函数。对于异步函数,会在状态更新后执行,不会阻塞状态更新,尽可能在外部使用 useEffect 处理异步副作用。
|
|
6
|
+
* @en State callback function. Async callbacks run after the state update without blocking it; prefer useEffect for async side effects.
|
|
14
7
|
* @template T The type of the state / 状态的类型
|
|
15
8
|
* @param newState The new state value / 新的状态值
|
|
16
9
|
* @param prevState The previous state value / 之前的状态值
|
|
17
10
|
*/
|
|
18
|
-
export type
|
|
19
|
-
newState: T,
|
|
20
|
-
prevState: T
|
|
21
|
-
) => any | Promise<any>;
|
|
11
|
+
export type ExternalStateCallback<T> = (newState: T, prevState: T) => any | Promise<any>
|
|
22
12
|
|
|
23
13
|
/**
|
|
24
14
|
* @en Transform functions for getting and setting state
|
|
@@ -31,12 +21,12 @@ export interface Transform<T, U = T> {
|
|
|
31
21
|
* @en Transform function for getting state
|
|
32
22
|
* @zh 获取状态时的转换函数
|
|
33
23
|
*/
|
|
34
|
-
get?: (state: T) => U
|
|
24
|
+
get?: (state: T) => U
|
|
35
25
|
/**
|
|
36
26
|
* @en Transform function for setting state
|
|
37
27
|
* @zh 设置状态时的转换函数
|
|
38
28
|
*/
|
|
39
|
-
set?: (value: U) => T
|
|
29
|
+
set?: (value: U) => T
|
|
40
30
|
}
|
|
41
31
|
|
|
42
32
|
/**
|
|
@@ -47,15 +37,20 @@ export interface Transform<T, U = T> {
|
|
|
47
37
|
*/
|
|
48
38
|
export interface ExternalStateOptions<T, U = T> {
|
|
49
39
|
/**
|
|
50
|
-
* @en
|
|
51
|
-
* @zh
|
|
40
|
+
* @en Callback invoked on every `set` call, even when the value is unchanged
|
|
41
|
+
* @zh 每次调用 `set` 后触发,即使值未发生变化
|
|
52
42
|
*/
|
|
53
|
-
|
|
43
|
+
onSet?: ExternalStateCallback<T>
|
|
44
|
+
/**
|
|
45
|
+
* @en Callback invoked only when the stored value actually changes
|
|
46
|
+
* @zh 仅在内部存储值发生变化时触发
|
|
47
|
+
*/
|
|
48
|
+
onChange?: ExternalStateCallback<T>
|
|
54
49
|
/**
|
|
55
50
|
* @en Transform functions for getting and setting state
|
|
56
51
|
* @zh 用于获取和设置状态的转换函数
|
|
57
52
|
*/
|
|
58
|
-
transform?: Transform<T, U
|
|
53
|
+
transform?: Transform<T, U>
|
|
59
54
|
}
|
|
60
55
|
|
|
61
56
|
/**
|
|
@@ -70,31 +65,31 @@ export interface ExternalState<T, U = T> {
|
|
|
70
65
|
* @zh 获取当前状态值
|
|
71
66
|
* @returns The current state value (transformed if transform.get is provided) / 当前状态值(如果提供了 transform.get 则进行转换)
|
|
72
67
|
*/
|
|
73
|
-
get: () => U
|
|
68
|
+
get: () => U
|
|
74
69
|
|
|
75
70
|
/**
|
|
76
71
|
* @en Set a new state value
|
|
77
72
|
* @zh 设置新的状态值
|
|
78
73
|
* @param newState The new state value or a function that returns it / 新的状态值或返回新状态的函数
|
|
79
74
|
*/
|
|
80
|
-
set: (newState: U | ((prevState: U) => U)) => void
|
|
75
|
+
set: (newState: U | ((prevState: U) => U)) => void
|
|
81
76
|
|
|
82
77
|
/**
|
|
83
78
|
* @en React Hook for using external state in components
|
|
84
79
|
* @zh 在组件中使用外部状态的 React Hook
|
|
85
|
-
* @returns Array containing current
|
|
80
|
+
* @returns Array containing current state and update function, similar to useState / 包含当前状态和更新函数的数组,类似于 useState
|
|
86
81
|
*/
|
|
87
|
-
use: () => [U, (newState: U | ((prevState: U) => U)) => void]
|
|
82
|
+
use: () => [U, (newState: U | ((prevState: U) => U)) => void]
|
|
88
83
|
|
|
89
84
|
/**
|
|
90
85
|
* @zh use的变体,只获取value.
|
|
91
86
|
* @en A variant of use that only gets the value.
|
|
92
87
|
*/
|
|
93
|
-
useGetter: () => U
|
|
88
|
+
useGetter: () => U
|
|
94
89
|
}
|
|
95
90
|
|
|
96
91
|
export interface ExternalWithKernel<T, U = T> extends ExternalState<T, U> {
|
|
97
|
-
__listeners:
|
|
92
|
+
__listeners: (() => void)[]
|
|
98
93
|
}
|
|
99
94
|
|
|
100
95
|
/**
|
|
@@ -103,7 +98,7 @@ export interface ExternalWithKernel<T, U = T> extends ExternalState<T, U> {
|
|
|
103
98
|
* ```tsx
|
|
104
99
|
* // Create an app-level theme state with options
|
|
105
100
|
* const themeState = createExternalState('light', {
|
|
106
|
-
*
|
|
101
|
+
* onChange: (newState, prevState) => console.log(`Theme changed from ${prevState} to ${newState}`),
|
|
107
102
|
* transform: {
|
|
108
103
|
* get: (state) => state.toUpperCase(),
|
|
109
104
|
* set: (value) => value.toLowerCase()
|
|
@@ -130,117 +125,120 @@ export interface ExternalWithKernel<T, U = T> extends ExternalState<T, U> {
|
|
|
130
125
|
*/
|
|
131
126
|
export function createExternalState<T, U = T>(
|
|
132
127
|
initialState: T | (() => T),
|
|
133
|
-
options: ExternalStateOptions<T, U> = {}
|
|
128
|
+
options: ExternalStateOptions<T, U> = {},
|
|
134
129
|
): ExternalState<T, U> {
|
|
135
|
-
let state: T =
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
130
|
+
let state: T = typeof initialState === 'function' ? (initialState as () => T)() : initialState
|
|
131
|
+
|
|
132
|
+
const storeListeners: (() => void)[] = []
|
|
133
|
+
const {onSet, onChange, transform} = options
|
|
139
134
|
|
|
140
|
-
const
|
|
141
|
-
|
|
135
|
+
const runCallback = (callback: ExternalStateCallback<T> | undefined, newState: T, prevState: T) => {
|
|
136
|
+
if (!callback) return
|
|
137
|
+
safePromiseTry(callback, newState, prevState).catch((error) => {
|
|
138
|
+
console.error('Error in external state callback, Please do it within side effects:', error)
|
|
139
|
+
})
|
|
140
|
+
}
|
|
142
141
|
|
|
143
142
|
const get = () => {
|
|
144
|
-
const currentState = state
|
|
145
|
-
return transform?.get
|
|
146
|
-
|
|
147
|
-
: (currentState as unknown as U);
|
|
148
|
-
};
|
|
143
|
+
const currentState = state
|
|
144
|
+
return transform?.get ? transform.get(currentState) : (currentState as unknown as U)
|
|
145
|
+
}
|
|
149
146
|
|
|
150
147
|
const set = (newState: U | ((prevState: U) => U)) => {
|
|
151
|
-
const prevState = state
|
|
148
|
+
const prevState = state
|
|
152
149
|
const transformedPrevState = transform?.get
|
|
153
150
|
? transform.get(prevState)
|
|
154
|
-
: (prevState as unknown as U)
|
|
151
|
+
: (prevState as unknown as U)
|
|
155
152
|
state = transform?.set
|
|
156
153
|
? transform.set(
|
|
157
|
-
typeof newState ===
|
|
154
|
+
typeof newState === 'function'
|
|
158
155
|
? (newState as (prev: U) => U)(transformedPrevState)
|
|
159
|
-
: newState
|
|
156
|
+
: newState,
|
|
160
157
|
)
|
|
161
|
-
: ((typeof newState ===
|
|
158
|
+
: ((typeof newState === 'function'
|
|
162
159
|
? (newState as (prev: U) => U)(transformedPrevState)
|
|
163
|
-
: newState) as unknown as T)
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
error
|
|
171
|
-
);
|
|
172
|
-
});
|
|
160
|
+
: newState) as unknown as T)
|
|
161
|
+
|
|
162
|
+
storeListeners.forEach((listener) => listener())
|
|
163
|
+
|
|
164
|
+
runCallback(onSet, state, prevState)
|
|
165
|
+
if (!Object.is(state, prevState)) {
|
|
166
|
+
runCallback(onChange, state, prevState)
|
|
173
167
|
}
|
|
174
|
-
}
|
|
168
|
+
}
|
|
175
169
|
|
|
176
170
|
const use = () => {
|
|
177
|
-
const
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
171
|
+
const localState = useSyncExternalStore(
|
|
172
|
+
(onStoreChange) => {
|
|
173
|
+
storeListeners.push(onStoreChange)
|
|
174
|
+
return () => {
|
|
175
|
+
const index = storeListeners.indexOf(onStoreChange)
|
|
176
|
+
if (index > -1) {
|
|
177
|
+
storeListeners.splice(index, 1)
|
|
178
|
+
}
|
|
185
179
|
}
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
180
|
+
},
|
|
181
|
+
() => state,
|
|
182
|
+
() => state,
|
|
183
|
+
)
|
|
184
|
+
|
|
185
|
+
return [transform?.get ? transform.get(localState) : (localState as unknown as U), set] as [
|
|
186
|
+
U,
|
|
187
|
+
(newState: U | ((prevState: U) => U)) => void,
|
|
188
|
+
]
|
|
189
|
+
}
|
|
194
190
|
|
|
195
191
|
const useGetter = () => {
|
|
196
|
-
const [value] = use()
|
|
197
|
-
return value
|
|
198
|
-
}
|
|
192
|
+
const [value] = use()
|
|
193
|
+
return value
|
|
194
|
+
}
|
|
199
195
|
|
|
200
196
|
//@ts-expect-error ignore
|
|
201
|
-
return {
|
|
197
|
+
return {get, set, use, useGetter, __listeners: storeListeners}
|
|
202
198
|
}
|
|
203
199
|
|
|
204
200
|
export interface StorageStateOptions<T, U> {
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
201
|
+
onSet?: ExternalStateCallback<T>
|
|
202
|
+
onChange?: ExternalStateCallback<T>
|
|
203
|
+
transform?: Transform<T, U>
|
|
204
|
+
storageType: 'local' | 'session'
|
|
208
205
|
}
|
|
209
206
|
|
|
210
207
|
export function createStorageState<T, U = T>(
|
|
211
208
|
key: string,
|
|
212
209
|
initialState: T,
|
|
213
|
-
options?: StorageStateOptions<T, U
|
|
210
|
+
options?: StorageStateOptions<T, U>,
|
|
214
211
|
) {
|
|
215
|
-
const {
|
|
216
|
-
let _initState: T = initialState
|
|
217
|
-
|
|
212
|
+
const {storageType = 'local', onSet, onChange, transform} = options ?? {}
|
|
213
|
+
let _initState: T = initialState
|
|
214
|
+
|
|
218
215
|
// 只在客户端环境中读取存储
|
|
219
216
|
if (typeof window !== 'undefined') {
|
|
220
|
-
const storage = storageType ===
|
|
221
|
-
const storedValue = storage.getItem(key)
|
|
217
|
+
const storage = storageType === 'local' ? localStorage : sessionStorage
|
|
218
|
+
const storedValue = storage.getItem(key)
|
|
222
219
|
if (storedValue) {
|
|
223
220
|
try {
|
|
224
|
-
_initState = JSON.parse(storedValue)
|
|
221
|
+
_initState = JSON.parse(storedValue)
|
|
225
222
|
} catch (error) {
|
|
226
223
|
console.warn(
|
|
227
224
|
`Failed to parse ${storageType}Storage value for key "${key}", using initial state:`,
|
|
228
|
-
error
|
|
229
|
-
)
|
|
230
|
-
_initState = initialState
|
|
225
|
+
error,
|
|
226
|
+
)
|
|
227
|
+
_initState = initialState
|
|
231
228
|
}
|
|
232
229
|
}
|
|
233
230
|
}
|
|
234
|
-
|
|
231
|
+
|
|
235
232
|
return createExternalState(_initState, {
|
|
236
|
-
|
|
233
|
+
onSet: (newState, prevState) => {
|
|
237
234
|
// 只在客户端环境中写入存储
|
|
238
235
|
if (typeof window !== 'undefined') {
|
|
239
|
-
const storage = storageType ===
|
|
240
|
-
storage.setItem(key, JSON.stringify(newState))
|
|
236
|
+
const storage = storageType === 'local' ? localStorage : sessionStorage
|
|
237
|
+
storage.setItem(key, JSON.stringify(newState))
|
|
241
238
|
}
|
|
242
|
-
|
|
239
|
+
onSet?.(newState, prevState)
|
|
243
240
|
},
|
|
241
|
+
onChange,
|
|
244
242
|
transform,
|
|
245
|
-
})
|
|
243
|
+
})
|
|
246
244
|
}
|
package/src/utils/index.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
export * from
|
|
2
|
-
export * from
|
|
3
|
-
export * from
|
|
4
|
-
export * from
|
|
5
|
-
export * from
|
|
6
|
-
export * from
|
|
7
|
-
export * from
|
|
1
|
+
export * from './createExternalState'
|
|
2
|
+
export * from './cx'
|
|
3
|
+
export * from './reactUtils'
|
|
4
|
+
export * from './sundry'
|
|
5
|
+
export * from './promise'
|
|
6
|
+
export * from './constants'
|
|
7
|
+
export * from './ruleChecker'
|
package/src/utils/promise.ts
CHANGED
|
@@ -16,37 +16,37 @@ function promiseTry<T, U extends unknown[]>(
|
|
|
16
16
|
...args: U
|
|
17
17
|
): Promise<Awaited<T>> {
|
|
18
18
|
try {
|
|
19
|
-
const result = callbackFn(...args)
|
|
19
|
+
const result = callbackFn(...args) // Call the callback function
|
|
20
20
|
// Check if the result is a PromiseLike (Promise)
|
|
21
21
|
if (result instanceof Promise) {
|
|
22
|
-
return result
|
|
22
|
+
return result // If it's a promise, return it directly
|
|
23
23
|
}
|
|
24
24
|
// If the result is not a promise, resolve it with Promise.resolve
|
|
25
|
-
return Promise.resolve(result as Awaited<T>)
|
|
25
|
+
return Promise.resolve(result as Awaited<T>)
|
|
26
26
|
} catch (error) {
|
|
27
27
|
// If the callback throws an error, reject the promise
|
|
28
|
-
return Promise.reject(error)
|
|
28
|
+
return Promise.reject(error)
|
|
29
29
|
}
|
|
30
30
|
}
|
|
31
31
|
|
|
32
32
|
export const safePromiseTry = (() => {
|
|
33
|
-
if (typeof Promise.try ===
|
|
34
|
-
return Promise.try.bind(Promise)
|
|
33
|
+
if (typeof Promise.try === 'function') {
|
|
34
|
+
return Promise.try.bind(Promise)
|
|
35
35
|
}
|
|
36
|
-
return promiseTry
|
|
37
|
-
})()
|
|
36
|
+
return promiseTry
|
|
37
|
+
})()
|
|
38
38
|
|
|
39
39
|
export const safePromiseWithResolvers = (() => {
|
|
40
|
-
if (typeof Promise.withResolvers ===
|
|
41
|
-
return Promise.withResolvers.bind(Promise)
|
|
40
|
+
if (typeof Promise.withResolvers === 'function') {
|
|
41
|
+
return Promise.withResolvers.bind(Promise)
|
|
42
42
|
}
|
|
43
43
|
return <T>() => {
|
|
44
|
-
let resolve!: (value: T) => void
|
|
45
|
-
let reject!: (reason?: any) => void
|
|
44
|
+
let resolve!: (value: T) => void
|
|
45
|
+
let reject!: (reason?: any) => void
|
|
46
46
|
const promise = new Promise((res, rej) => {
|
|
47
|
-
resolve = res
|
|
48
|
-
reject = rej
|
|
49
|
-
})
|
|
50
|
-
return {
|
|
51
|
-
}
|
|
52
|
-
})()
|
|
47
|
+
resolve = res
|
|
48
|
+
reject = rej
|
|
49
|
+
})
|
|
50
|
+
return {promise, resolve, reject}
|
|
51
|
+
}
|
|
52
|
+
})()
|