@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.
@@ -1,99 +1,88 @@
1
- import { useEffect, useState } from "react";
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 "base";
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
- getCurrentBreakpoint(breakpointDesc, window.innerWidth)
27
- );
28
+ // lazy initializer:SSR 环境下 window 不存在,fallback 到 "base"
29
+ () => (isBrowser ? getCurrentBreakpoint(breakpointDesc, window.innerWidth) : 'base'),
30
+ )
28
31
 
29
32
  useEffect(() => {
30
- let mediaQueries: MediaQueryList[] = [];
31
- let listeners: (() => void)[] = [];
33
+ if (!isBrowser) return
34
+
35
+ let mediaQueries: MediaQueryList[] = []
36
+ let listeners: (() => void)[] = []
32
37
 
33
38
  const updateMediaQueries = () => {
34
- // 清理旧的监听器
35
- listeners.forEach((removeListener) => removeListener());
36
- mediaQueries = [];
37
- listeners = [];
39
+ listeners.forEach((removeListener) => removeListener())
40
+ mediaQueries = []
41
+ listeners = []
38
42
 
39
- // 计算当前断点
40
- const currentBp = getCurrentBreakpoint(breakpointDesc, window.innerWidth);
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
- // 监听下一个断点(min-width)
47
- const nextBp = breakpoints[currentIndex + 1];
48
- if (nextBp && breakpointDesc[nextBp] !== undefined) {
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
- `(min-width: ${nextMinWidth}px)`
53
- );
54
- mediaQueries.push(mediaQuery);
55
- const handleMediaChange = () => updateMediaQueries();
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
- // 监听上一个断点(max-width)
68
- const prevBp = breakpoints[currentIndex - 1];
69
- if (prevBp && breakpointDesc[prevBp] !== undefined) {
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
- `(max-width: ${prevMinWidth - 1}px)`
74
- );
75
- mediaQueries.push(mediaQuery);
76
- const handleMediaChange = () => updateMediaQueries();
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
- }, [breakpointDesc]);
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 "./components/ProcessControl";
2
- export * from "./components/Sundry";
3
- export * from "./components/Struct";
1
+ export * from './components/ProcessControl'
2
+ export * from './components/Sundry'
3
+ export * from './components/Struct'
4
4
 
5
- export * from "./hooks";
5
+ export * from './hooks'
6
6
 
7
- export * from "./utils";
7
+ export * from './utils'
@@ -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
- "2xl": 1536,
19
- "3xl": 1920,
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 mockSideEffect = vi.fn((...args) => void 0);
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
- sideEffect: mockSideEffect,
127
+ onSet: mockOnSet,
128
128
  });
129
129
  state.set("updated");
130
- expect(mockSideEffect).toHaveBeenCalledTimes(1);
131
- expect(mockSideEffect).toHaveBeenCalledWith("updated", initialState);
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(mockSideEffect).toHaveBeenCalledTimes(2);
134
- expect(mockSideEffect).toHaveBeenCalledWith("updated2", "updated");
152
+ expect(mockOnChange).toHaveBeenCalledTimes(2);
153
+ expect(mockOnChange).toHaveBeenCalledWith("updated2", "updated");
135
154
  });
136
155
 
137
- it("测试异步副作用函数", async () => {
138
- const mockAsyncSideEffect = vi.fn().mockResolvedValue(undefined);
156
+ it("测试异步 onSet 回调", async () => {
157
+ const mockAsyncOnSet = vi.fn().mockResolvedValue(undefined);
139
158
  const initialState: string = "initial";
140
159
  const state = createExternalState(initialState, {
141
- sideEffect: mockAsyncSideEffect,
160
+ onSet: mockAsyncOnSet,
142
161
  });
143
162
 
144
163
  state.set("updated");
145
- expect(mockAsyncSideEffect).toHaveBeenCalledTimes(1);
146
- expect(mockAsyncSideEffect).toHaveBeenCalledWith("updated", initialState);
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 mockSideEffect = vi.fn();
343
+ it("测试存储 onSet 回调", () => {
344
+ const mockOnSet = vi.fn();
326
345
  const state = createStorageState("test-key", "initial" as string, {
327
346
  storageType: "local",
328
- sideEffect: mockSideEffect,
347
+ onSet: mockOnSet,
329
348
  });
330
349
 
331
350
  state.set("updated");
332
351
 
333
- expect(mockSideEffect).toHaveBeenCalledTimes(1);
334
- expect(mockSideEffect).toHaveBeenCalledWith("updated");
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 React from "react";
2
- import { safePromiseTry } from "./promise";
1
+ import {useSyncExternalStore} from 'react'
2
+ import {safePromiseTry} from './promise'
3
3
 
4
4
  /**
5
- * @en Callback function type for state change listeners
6
- * @zh 状态变更监听器的回调函数类型
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 ExternalSideEffect<T> = (
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 Side effect function to run after state changes
51
- * @zh 状态变更后运行的副作用函数
40
+ * @en Callback invoked on every `set` call, even when the value is unchanged
41
+ * @zh 每次调用 `set` 后触发,即使值未发生变化
52
42
  */
53
- sideEffect?: ExternalSideEffect<T>;
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钣金龙8国际唯一官网 current state and update function, similar to useState / 包含当前状态和更新函数的数组,类似于 useState
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: CreateStateListener<T>[];
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
- * sideEffect: (newState, prevState) => console.log(`Theme changed from ${prevState} to ${newState}`),
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
- typeof initialState === "function"
137
- ? (initialState as () => T)()
138
- : initialState;
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 listeners: CreateStateListener<T>[] = [];
141
- const { sideEffect, transform } = options;
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
- ? transform.get(currentState)
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 === "function"
154
+ typeof newState === 'function'
158
155
  ? (newState as (prev: U) => U)(transformedPrevState)
159
- : newState
156
+ : newState,
160
157
  )
161
- : ((typeof newState === "function"
158
+ : ((typeof newState === 'function'
162
159
  ? (newState as (prev: U) => U)(transformedPrevState)
163
- : newState) as unknown as T);
164
-
165
- listeners.forEach((listener) => listener(state));
166
- if (sideEffect) {
167
- safePromiseTry(sideEffect, state, prevState).catch((error) => {
168
- console.error(
169
- "Error in external state side effect, Please do it within side effects:",
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 [localState, setLocalState] = React.useState<T>(state);
178
-
179
- React.useEffect(() => {
180
- listeners.push(setLocalState);
181
- return () => {
182
- const index = listeners.indexOf(setLocalState);
183
- if (index > -1) {
184
- listeners.splice(index, 1);
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
- return [
190
- transform?.get ? transform.get(localState) : (localState as unknown as U),
191
- set,
192
- ] as [U, (newState: U | ((prevState: U) => U)) => void];
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 { get, set, use, useGetter, __listeners: listeners };
197
+ return {get, set, use, useGetter, __listeners: storeListeners}
202
198
  }
203
199
 
204
200
  export interface StorageStateOptions<T, U> {
205
- sideEffect?: (newState: T) => void;
206
- transform?: Transform<T, U>;
207
- storageType: "local" | "session";
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 { storageType = "local", sideEffect, transform } = options ?? {};
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 === "local" ? localStorage : sessionStorage;
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
- sideEffect: (newState) => {
233
+ onSet: (newState, prevState) => {
237
234
  // 只在客户端环境中写入存储
238
235
  if (typeof window !== 'undefined') {
239
- const storage = storageType === "local" ? localStorage : sessionStorage;
240
- storage.setItem(key, JSON.stringify(newState));
236
+ const storage = storageType === 'local' ? localStorage : sessionStorage
237
+ storage.setItem(key, JSON.stringify(newState))
241
238
  }
242
- sideEffect?.(newState);
239
+ onSet?.(newState, prevState)
243
240
  },
241
+ onChange,
244
242
  transform,
245
- });
243
+ })
246
244
  }
@@ -1,7 +1,7 @@
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";
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'
@@ -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); // Call the callback function
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; // If it's a promise, return it directly
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 === "function") {
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 === "function") {
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 { promise, resolve, reject };
51
- };
52
- })();
47
+ resolve = res
48
+ reject = rej
49
+ })
50
+ return {promise, resolve, reject}
51
+ }
52
+ })()