@wwog/react 1.3.12 → 1.3.13

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>>
@@ -1,12 +1,5 @@
1
- import React from "react";
2
- import { safePromiseTry } from "./promise";
3
-
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;
1
+ import {useSyncExternalStore} from 'react'
2
+ import {safePromiseTry} from './promise'
10
3
 
11
4
  /**
12
5
  * @zh 如果需要在变更状态时执行副作用,可以传入函数,对于异步函数,会在更改状态后执行,不会阻塞状态更新, 尽可能在外部使用useEffect处理异步副作用
@@ -15,10 +8,7 @@ export type CreateStateListener<T> = (state: T) => void;
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 ExternalSideEffect<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
  /**
@@ -50,12 +40,12 @@ export interface ExternalStateOptions<T, U = T> {
50
40
  * @en Side effect function to run after state changes
51
41
  * @zh 状态变更后运行的副作用函数
52
42
  */
53
- sideEffect?: ExternalSideEffect<T>;
43
+ sideEffect?: ExternalSideEffect<T>
54
44
  /**
55
45
  * @en Transform functions for getting and setting state
56
46
  * @zh 用于获取和设置状态的转换函数
57
47
  */
58
- transform?: Transform<T, U>;
48
+ transform?: Transform<T, U>
59
49
  }
60
50
 
61
51
  /**
@@ -70,31 +60,31 @@ export interface ExternalState<T, U = T> {
70
60
  * @zh 获取当前状态值
71
61
  * @returns The current state value (transformed if transform.get is provided) / 当前状态值(如果提供了 transform.get 则进行转换)
72
62
  */
73
- get: () => U;
63
+ get: () => U
74
64
 
75
65
  /**
76
66
  * @en Set a new state value
77
67
  * @zh 设置新的状态值
78
68
  * @param newState The new state value or a function that returns it / 新的状态值或返回新状态的函数
79
69
  */
80
- set: (newState: U | ((prevState: U) => U)) => void;
70
+ set: (newState: U | ((prevState: U) => U)) => void
81
71
 
82
72
  /**
83
73
  * @en React Hook for using external state in components
84
74
  * @zh 在组件中使用外部状态的 React Hook
85
75
  * @returns Array containing current钣金龙8国际唯一官网 current state and update function, similar to useState / 包含当前状态和更新函数的数组,类似于 useState
86
76
  */
87
- use: () => [U, (newState: U | ((prevState: U) => U)) => void];
77
+ use: () => [U, (newState: U | ((prevState: U) => U)) => void]
88
78
 
89
79
  /**
90
80
  * @zh use的变体,只获取value.
91
81
  * @en A variant of use that only gets the value.
92
82
  */
93
- useGetter: () => U;
83
+ useGetter: () => U
94
84
  }
95
85
 
96
86
  export interface ExternalWithKernel<T, U = T> extends ExternalState<T, U> {
97
- __listeners: CreateStateListener<T>[];
87
+ __listeners: (() => void)[]
98
88
  }
99
89
 
100
90
  /**
@@ -130,117 +120,114 @@ export interface ExternalWithKernel<T, U = T> extends ExternalState<T, U> {
130
120
  */
131
121
  export function createExternalState<T, U = T>(
132
122
  initialState: T | (() => T),
133
- options: ExternalStateOptions<T, U> = {}
123
+ options: ExternalStateOptions<T, U> = {},
134
124
  ): ExternalState<T, U> {
135
- let state: T =
136
- typeof initialState === "function"
137
- ? (initialState as () => T)()
138
- : initialState;
125
+ let state: T = typeof initialState === 'function' ? (initialState as () => T)() : initialState
139
126
 
140
- const listeners: CreateStateListener<T>[] = [];
141
- const { sideEffect, transform } = options;
127
+ const storeListeners: (() => void)[] = []
128
+ const {sideEffect, transform} = options
142
129
 
143
130
  const get = () => {
144
- const currentState = state;
145
- return transform?.get
146
- ? transform.get(currentState)
147
- : (currentState as unknown as U);
148
- };
131
+ const currentState = state
132
+ return transform?.get ? transform.get(currentState) : (currentState as unknown as U)
133
+ }
149
134
 
150
135
  const set = (newState: U | ((prevState: U) => U)) => {
151
- const prevState = state;
136
+ const prevState = state
152
137
  const transformedPrevState = transform?.get
153
138
  ? transform.get(prevState)
154
- : (prevState as unknown as U);
139
+ : (prevState as unknown as U)
155
140
  state = transform?.set
156
141
  ? transform.set(
157
- typeof newState === "function"
142
+ typeof newState === 'function'
158
143
  ? (newState as (prev: U) => U)(transformedPrevState)
159
- : newState
144
+ : newState,
160
145
  )
161
- : ((typeof newState === "function"
146
+ : ((typeof newState === 'function'
162
147
  ? (newState as (prev: U) => U)(transformedPrevState)
163
- : newState) as unknown as T);
148
+ : newState) as unknown as T)
164
149
 
165
- listeners.forEach((listener) => listener(state));
150
+ storeListeners.forEach((listener) => listener())
166
151
  if (sideEffect) {
167
152
  safePromiseTry(sideEffect, state, prevState).catch((error) => {
168
153
  console.error(
169
- "Error in external state side effect, Please do it within side effects:",
170
- error
171
- );
172
- });
154
+ 'Error in external state side effect, Please do it within side effects:',
155
+ error,
156
+ )
157
+ })
173
158
  }
174
- };
159
+ }
175
160
 
176
161
  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);
162
+ const localState = useSyncExternalStore(
163
+ (onStoreChange) => {
164
+ storeListeners.push(onStoreChange)
165
+ return () => {
166
+ const index = storeListeners.indexOf(onStoreChange)
167
+ if (index > -1) {
168
+ storeListeners.splice(index, 1)
169
+ }
185
170
  }
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
- };
171
+ },
172
+ () => state,
173
+ () => state,
174
+ )
175
+
176
+ return [transform?.get ? transform.get(localState) : (localState as unknown as U), set] as [
177
+ U,
178
+ (newState: U | ((prevState: U) => U)) => void,
179
+ ]
180
+ }
194
181
 
195
182
  const useGetter = () => {
196
- const [value] = use();
197
- return value;
198
- };
183
+ const [value] = use()
184
+ return value
185
+ }
199
186
 
200
187
  //@ts-expect-error ignore
201
- return { get, set, use, useGetter, __listeners: listeners };
188
+ return {get, set, use, useGetter, __listeners: storeListeners}
202
189
  }
203
190
 
204
191
  export interface StorageStateOptions<T, U> {
205
- sideEffect?: (newState: T) => void;
206
- transform?: Transform<T, U>;
207
- storageType: "local" | "session";
192
+ sideEffect?: (newState: T) => void
193
+ transform?: Transform<T, U>
194
+ storageType: 'local' | 'session'
208
195
  }
209
196
 
210
197
  export function createStorageState<T, U = T>(
211
198
  key: string,
212
199
  initialState: T,
213
- options?: StorageStateOptions<T, U>
200
+ options?: StorageStateOptions<T, U>,
214
201
  ) {
215
- const { storageType = "local", sideEffect, transform } = options ?? {};
216
- let _initState: T = initialState;
217
-
202
+ const {storageType = 'local', sideEffect, transform} = options ?? {}
203
+ let _initState: T = initialState
204
+
218
205
  // 只在客户端环境中读取存储
219
206
  if (typeof window !== 'undefined') {
220
- const storage = storageType === "local" ? localStorage : sessionStorage;
221
- const storedValue = storage.getItem(key);
207
+ const storage = storageType === 'local' ? localStorage : sessionStorage
208
+ const storedValue = storage.getItem(key)
222
209
  if (storedValue) {
223
210
  try {
224
- _initState = JSON.parse(storedValue);
211
+ _initState = JSON.parse(storedValue)
225
212
  } catch (error) {
226
213
  console.warn(
227
214
  `Failed to parse ${storageType}Storage value for key "${key}", using initial state:`,
228
- error
229
- );
230
- _initState = initialState;
215
+ error,
216
+ )
217
+ _initState = initialState
231
218
  }
232
219
  }
233
220
  }
234
-
221
+
235
222
  return createExternalState(_initState, {
236
223
  sideEffect: (newState) => {
237
224
  // 只在客户端环境中写入存储
238
225
  if (typeof window !== 'undefined') {
239
- const storage = storageType === "local" ? localStorage : sessionStorage;
240
- storage.setItem(key, JSON.stringify(newState));
226
+ const storage = storageType === 'local' ? localStorage : sessionStorage
227
+ storage.setItem(key, JSON.stringify(newState))
241
228
  }
242
- sideEffect?.(newState);
229
+ sideEffect?.(newState)
243
230
  },
244
231
  transform,
245
- });
232
+ })
246
233
  }
@@ -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
+ })()