react-solidlike 2.4.0 → 2.5.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.
package/README.md CHANGED
@@ -1,80 +1,80 @@
1
1
  # react-solidlike
2
2
 
3
- [English](./README.en.md) | 中文
3
+ English | [中文](./README.zh.md)
4
4
 
5
- 声明式 React 控制流组件库,灵感来源于 Solid.js。用于替代 JSX 中的三元表达式和 `array.map()`,让你的组件代码更加清晰易读。支持 React React Native
5
+ Declarative React control flow components inspired by Solid.js. Replaces ternary expressions and `array.map()` in JSX, making your component code cleaner and more readable. Supports React and React Native.
6
6
 
7
- ## 安装
7
+ ## Installation
8
8
 
9
9
  ```bash
10
10
  npm install react-solidlike
11
- #
11
+ # or
12
12
  bun add react-solidlike
13
13
  ```
14
14
 
15
- ## 组件
15
+ ## Components
16
16
 
17
- ### `<Show>` - 条件渲染
17
+ ### `<Show>` - Conditional Rendering
18
18
 
19
- 替代三元表达式进行条件渲染。
19
+ Replace ternary expressions for conditional rendering.
20
20
 
21
21
  ```tsx
22
22
  import { Show } from "react-solidlike";
23
23
 
24
- // 基础用法
24
+ // Basic usage
25
25
  <Show when={isLoggedIn}>
26
26
  <UserProfile />
27
27
  </Show>
28
28
 
29
- // fallback
29
+ // With fallback
30
30
  <Show when={isLoggedIn} fallback={<LoginButton />}>
31
31
  <UserProfile />
32
32
  </Show>
33
33
 
34
- // 使用 render props 获取类型安全的值
34
+ // Using render props for type-safe value access
35
35
  <Show when={user}>
36
36
  {(user) => <UserProfile name={user.name} />}
37
37
  </Show>
38
38
 
39
- // onFallback 回调(用于重定向等副作用)
39
+ // With onFallback callback (for redirects and other side effects)
40
40
  <Show when={isAuthenticated} fallback={<Loading />} onFallback={() => navigate('/login')}>
41
41
  <Dashboard />
42
42
  </Show>
43
43
  ```
44
44
 
45
- ### `<For>` - 列表渲染
45
+ ### `<For>` - List Rendering
46
46
 
47
- 替代 `array.map()` 进行列表渲染。
47
+ Replace `array.map()` for list rendering.
48
48
 
49
49
  ```tsx
50
50
  import { For } from "react-solidlike";
51
51
 
52
- // 基础用法
52
+ // Basic usage
53
53
  <For each={items}>
54
54
  {(item) => <ListItem {...item} />}
55
55
  </For>
56
56
 
57
- // keyExtractor
57
+ // With keyExtractor
58
58
  <For each={users} keyExtractor={(user) => user.id}>
59
59
  {(user) => <UserCard user={user} />}
60
60
  </For>
61
61
 
62
- // fallback 处理空数组
62
+ // With fallback for empty arrays
63
63
  <For each={items} fallback={<EmptyState />}>
64
64
  {(item, index) => <ListItem item={item} index={index} />}
65
65
  </For>
66
66
 
67
- // 使用 wrapper 包装元素
67
+ // With wrapper element
68
68
  <For each={items} wrapper={<ul className="list" />}>
69
69
  {(item) => <li>{item.name}</li>}
70
70
  </For>
71
71
 
72
- // 倒序渲染
72
+ // Reverse rendering
73
73
  <For each={messages} reverse>
74
74
  {(msg) => <Message {...msg} />}
75
75
  </For>
76
76
 
77
- // 使用 array 参数获取上下文信息
77
+ // Using array parameter for context
78
78
  <For each={steps}>
79
79
  {(step, index, array) => (
80
80
  <Step
@@ -86,9 +86,9 @@ import { For } from "react-solidlike";
86
86
  </For>
87
87
  ```
88
88
 
89
- ### `<Switch>` / `<Match>` / `<Default>` - 多分支渲染
89
+ ### `<Switch>` / `<Match>` / `<Default>` - Multi-branch Rendering
90
90
 
91
- 替代多个 `if-else` `switch` 语句。
91
+ Replace multiple `if-else` or `switch` statements.
92
92
 
93
93
  ```tsx
94
94
  import { Switch, Match, Default } from "react-solidlike";
@@ -109,19 +109,19 @@ import { Switch, Match, Default } from "react-solidlike";
109
109
  </Switch>
110
110
  ```
111
111
 
112
- ### `<Await>` - 异步等待
112
+ ### `<Await>` - Async Rendering
113
113
 
114
- 等待 Promise resolve 后渲染内容。
114
+ Wait for Promise to resolve before rendering.
115
115
 
116
116
  ```tsx
117
117
  import { Await } from "react-solidlike";
118
118
 
119
- // 基础用法
119
+ // Basic usage
120
120
  <Await promise={fetchUser()} loading={<Spinner />}>
121
121
  {(user) => <UserProfile user={user} />}
122
122
  </Await>
123
123
 
124
- // 带错误处理
124
+ // With error handling
125
125
  <Await
126
126
  promise={fetchData()}
127
127
  loading={<Loading />}
@@ -130,40 +130,40 @@ import { Await } from "react-solidlike";
130
130
  {(data) => <DataView data={data} />}
131
131
  </Await>
132
132
 
133
- // 支持非 Promise 值(用于缓存场景)
133
+ // Supports non-Promise values (for caching scenarios)
134
134
  <Await promise={cache ?? fetchData()} loading={<Spinner />}>
135
135
  {(data) => <DataView data={data} />}
136
136
  </Await>
137
137
  ```
138
138
 
139
- ### `<Repeat>` - 重复渲染
139
+ ### `<Repeat>` - Repeat Rendering
140
140
 
141
- 替代 `Array.from({ length: n }).map()`。
141
+ Replace `Array.from({ length: n }).map()`.
142
142
 
143
143
  ```tsx
144
144
  import { Repeat } from "react-solidlike";
145
145
 
146
- // 渲染星级评分
146
+ // Render star ratings
147
147
  <Repeat times={5}>
148
148
  {(i) => <Star key={i} filled={i < rating} />}
149
149
  </Repeat>
150
150
 
151
- // 生成骨架屏占位
151
+ // Generate skeleton placeholders
152
152
  <Repeat times={3}>
153
153
  {(i) => <SkeletonCard key={i} />}
154
154
  </Repeat>
155
155
 
156
- // 使用 wrapper 包装元素
156
+ // With wrapper element
157
157
  <Repeat times={5} wrapper={<div className="stars" />}>
158
158
  {(i) => <Star key={i} />}
159
159
  </Repeat>
160
160
 
161
- // 倒序渲染
161
+ // Reverse rendering
162
162
  <Repeat times={5} reverse>
163
- {(i) => <div key={i}>倒序 {i}</div>}
163
+ {(i) => <div key={i}>Reversed {i}</div>}
164
164
  </Repeat>
165
165
 
166
- // 使用 length 参数显示进度
166
+ // Using length parameter for progress
167
167
  <Repeat times={totalSteps}>
168
168
  {(i, length) => (
169
169
  <Step key={i} current={i + 1} total={length} />
@@ -171,56 +171,56 @@ import { Repeat } from "react-solidlike";
171
171
  </Repeat>
172
172
  ```
173
173
 
174
- ### `<Split>` - 字符串切割渲染
174
+ ### `<Split>` - String Split Rendering
175
175
 
176
- 按分隔符切割字符串并渲染每个部分。
176
+ Split a string by separator and render each part.
177
177
 
178
178
  ```tsx
179
179
  import { Split } from "react-solidlike";
180
180
 
181
- // 基础用法 - 切割后不保留分隔符
181
+ // Basic usage - splits without keeping separator
182
182
  <Split string="a,b,c" separator=",">
183
183
  {(part) => <span>{part}</span>}
184
184
  </Split>
185
- // 渲染: ["a", "b", "c"]
185
+ // Renders: ["a", "b", "c"]
186
186
 
187
- // 保留分隔符
187
+ // Keep separator in result
188
188
  <Split string="9+5=(9+1)+4" separator="=" keepSeparator>
189
189
  {(part) => <span>{part}</span>}
190
190
  </Split>
191
- // 渲染: ["9+5", "=", "(9+1)+4"]
191
+ // Renders: ["9+5", "=", "(9+1)+4"]
192
192
 
193
- // 使用正则表达式分隔符
193
+ // Using RegExp separator
194
194
  <Split string="a1b2c3" separator={/\d/} keepSeparator>
195
195
  {(part) => <span>{part}</span>}
196
196
  </Split>
197
- // 渲染: ["a", "1", "b", "2", "c", "3"]
197
+ // Renders: ["a", "1", "b", "2", "c", "3"]
198
198
 
199
- // wrapper 包装元素
199
+ // With wrapper element
200
200
  <Split string="hello world" separator=" " wrapper={<div className="words" />}>
201
201
  {(word) => <span>{word}</span>}
202
202
  </Split>
203
203
 
204
- // fallback 处理空字符串
204
+ // With fallback for empty string
205
205
  <Split string={text} separator="," fallback={<EmptyState />}>
206
206
  {(part) => <Tag>{part}</Tag>}
207
207
  </Split>
208
208
 
209
- // 倒序渲染
209
+ // Reverse rendering
210
210
  <Split string="a,b,c" separator="," reverse>
211
211
  {(part) => <span>{part}</span>}
212
212
  </Split>
213
- // 渲染顺序: ["c", "b", "a"]
213
+ // Render order: ["c", "b", "a"]
214
214
  ```
215
215
 
216
- ### `<Dynamic>` - 动态组件
216
+ ### `<Dynamic>` - Dynamic Component
217
217
 
218
- 根据条件动态选择要渲染的组件类型。
218
+ Dynamically select component type based on conditions.
219
219
 
220
220
  ```tsx
221
221
  import { Dynamic } from "react-solidlike";
222
222
 
223
- // 动态选择按钮或链接
223
+ // Dynamic button or link
224
224
  <Dynamic
225
225
  component={href ? 'a' : 'button'}
226
226
  href={href}
@@ -229,13 +229,13 @@ import { Dynamic } from "react-solidlike";
229
229
  {label}
230
230
  </Dynamic>
231
231
 
232
- // 配合自定义组件
232
+ // With custom components
233
233
  <Dynamic
234
234
  component={isAdmin ? AdminPanel : UserPanel}
235
235
  user={currentUser}
236
236
  />
237
237
 
238
- // React Native 中使用
238
+ // React Native usage
239
239
  <Dynamic
240
240
  component={isPressable ? Pressable : View}
241
241
  onPress={handlePress}
@@ -244,19 +244,19 @@ import { Dynamic } from "react-solidlike";
244
244
  </Dynamic>
245
245
  ```
246
246
 
247
- ### `<ErrorBoundary>` - 错误边界
247
+ ### `<ErrorBoundary>` - Error Boundary
248
248
 
249
- 捕获子组件树中的 JavaScript 错误,防止整个应用崩溃。
249
+ Catch JavaScript errors in child component tree.
250
250
 
251
251
  ```tsx
252
252
  import { ErrorBoundary } from "react-solidlike";
253
253
 
254
- // 基础用法
254
+ // Basic usage
255
255
  <ErrorBoundary fallback={<ErrorPage />}>
256
256
  <App />
257
257
  </ErrorBoundary>
258
258
 
259
- // 使用 render props 获取错误信息和重置函数
259
+ // With render props for error info and reset function
260
260
  <ErrorBoundary
261
261
  fallback={(error, reset) => (
262
262
  <div>
@@ -268,15 +268,15 @@ import { ErrorBoundary } from "react-solidlike";
268
268
  <App />
269
269
  </ErrorBoundary>
270
270
 
271
- // resetKey 变化时自动重置
271
+ // Auto-reset when resetKey changes
272
272
  <ErrorBoundary fallback={<Error />} resetKey={userId}>
273
273
  <UserProfile />
274
274
  </ErrorBoundary>
275
275
  ```
276
276
 
277
- ### `<QueryBoundary>` - 查询边界
277
+ ### `<QueryBoundary>` - Query Boundary
278
278
 
279
- 处理异步查询的各种状态(加载中、错误、空数据、成功)。可与 `@tanstack/react-query`、SWRRTK Query 等配合使用。
279
+ Handle async query states (loading, error, empty, success). Works with `@tanstack/react-query`, SWR, RTK Query, etc.
280
280
 
281
281
  ```tsx
282
282
  import { QueryBoundary } from "react-solidlike";
@@ -306,28 +306,28 @@ function UserList() {
306
306
 
307
307
  #### Props
308
308
 
309
- | 属性 | 类型 | 描述 |
310
- |------|------|------|
311
- | `query` | `QueryResult<T>` | 查询结果对象 |
312
- | `loading` | `ReactNode` | 加载中显示 |
313
- | `error` | `ReactNode` | 错误时显示 |
314
- | `empty` | `ReactNode` | 空数据显示 |
315
- | `children` | `ReactNode \| (data: T) => ReactNode` | 成功时渲染 |
316
- | `isEmptyFn` | `(data: T) => boolean` | 自定义空判断 |
309
+ | Prop | Type | Description |
310
+ | ----------- | ------------------------------------- | --------------------- |
311
+ | `query` | `QueryResult<T>` | Query result object |
312
+ | `loading` | `ReactNode` | Loading state content |
313
+ | `error` | `ReactNode` | Error state content |
314
+ | `empty` | `ReactNode` | Empty state content |
315
+ | `children` | `ReactNode \| (data: T) => ReactNode` | Success content |
316
+ | `isEmptyFn` | `(data: T) => boolean` | Custom empty check |
317
317
 
318
- ### `<Once>` - 单次渲染
318
+ ### `<Once>` - Single Render
319
319
 
320
- 只渲染一次子元素,忽略后续更新。适用于昂贵的计算或不应重新渲染的内容。
320
+ Renders children only once and ignores subsequent updates. Useful for expensive computations or content that shouldn't re-render.
321
321
 
322
322
  ```tsx
323
323
  import { Once } from "react-solidlike";
324
324
 
325
- // 渲染昂贵的组件
325
+ // Render expensive component once
326
326
  <Once>
327
327
  <ExpensiveChart data={data} />
328
328
  </Once>
329
329
 
330
- // 防止父组件更新导致的重新渲染
330
+ // Prevent re-renders from parent updates
331
331
  function Parent() {
332
332
  const [count, setCount] = useState(0);
333
333
  return (
@@ -341,75 +341,113 @@ function Parent() {
341
341
  }
342
342
  ```
343
343
 
344
- ### `<ClientOnly>` - 仅客户端渲染
344
+ ### `<ClientOnly>` - Client-side Only Rendering
345
345
 
346
- 仅在客户端(hydration 之后)渲染子元素。适用于依赖浏览器 API 或需要避免 SSR hydration 不匹配的场景。
346
+ Renders children only on the client side (after hydration). Useful for components that rely on browser APIs or need to avoid SSR hydration mismatches.
347
347
 
348
348
  ```tsx
349
349
  import { ClientOnly } from "react-solidlike";
350
350
 
351
- // 基础用法
351
+ // Basic usage
352
352
  <ClientOnly>
353
353
  <BrowserOnlyComponent />
354
354
  </ClientOnly>
355
355
 
356
- // SSR 备选内容
356
+ // With SSR fallback
357
357
  <ClientOnly fallback={<Skeleton />}>
358
358
  <DynamicChart />
359
359
  </ClientOnly>
360
360
 
361
- // 使用渲染函数延迟求值(避免访问 window
361
+ // Using render function for lazy evaluation (avoid accessing window)
362
362
  <ClientOnly fallback={<Loading />}>
363
363
  {() => <ComponentUsingWindow width={window.innerWidth} />}
364
364
  </ClientOnly>
365
365
 
366
- // 避免 hydration 不匹配
366
+ // Avoid hydration mismatch
367
367
  <ClientOnly fallback={<span>--:--</span>}>
368
368
  <CurrentTime />
369
369
  </ClientOnly>
370
370
  ```
371
371
 
372
- ### `<Visible>` - 可见性渲染(仅 Web)
372
+ ### `<Timeout>` - Timeout Rendering
373
373
 
374
- 基于 IntersectionObserver 的可见性渲染,进入视口才渲染。在 React Native 或不支持的环境中会直接渲染 children(优雅降级)。
374
+ Shows or hides content after a specified delay. Useful for auto-dismissing notifications, delayed loading scenarios.
375
+
376
+ ```tsx
377
+ import { Timeout } from "react-solidlike";
378
+
379
+ // Show after delay (mode="after", default)
380
+ <Timeout ms={1000} mode="after" fallback={<Spinner />}>
381
+ <DelayedContent />
382
+ </Timeout>
383
+
384
+ // Hide after delay (mode="before")
385
+ <Timeout ms={3000} mode="before">
386
+ <Toast message="Success!" />
387
+ </Timeout>
388
+
389
+ // Auto-dismiss notification
390
+ <Timeout ms={5000} mode="before" onTimeout={() => console.log("dismissed")}>
391
+ <Notification type="success">Saved successfully</Notification>
392
+ </Timeout>
393
+
394
+ // Delayed render with loading state
395
+ <Timeout ms={2000} mode="after" fallback={<Skeleton />}>
396
+ <ExpensiveComponent />
397
+ </Timeout>
398
+ ```
399
+
400
+ #### Props
401
+
402
+ | Prop | Type | Description |
403
+ | ----------- | --------------------- | ------------------------------------------------------------------------------- |
404
+ | `ms` | `number` | Delay time in milliseconds |
405
+ | `mode` | `'after' \| 'before'` | `'after'` = show after delay, `'before'` = hide after delay (default `'after'`) |
406
+ | `children` | `ReactNode` | Content to render |
407
+ | `fallback` | `ReactNode` | Content to show while waiting (`after` mode only) |
408
+ | `onTimeout` | `() => void` | Callback when timeout occurs |
409
+
410
+ ### `<Visible>` - Visibility-based Rendering (Web only)
411
+
412
+ Renders children when entering viewport using IntersectionObserver. In React Native or unsupported environments, children are rendered directly (graceful degradation).
375
413
 
376
414
  ```tsx
377
415
  import { Visible } from "react-solidlike";
378
416
 
379
- // 基础用法 - 进入视口时渲染
417
+ // Basic usage - render when entering viewport
380
418
  <Visible>
381
419
  <HeavyComponent />
382
420
  </Visible>
383
421
 
384
- // 带占位符
422
+ // With placeholder
385
423
  <Visible fallback={<Skeleton />}>
386
424
  <Image src={url} />
387
425
  </Visible>
388
426
 
389
- // 提前预加载(rootMargin
427
+ // Preload before entering viewport (rootMargin)
390
428
  <Visible rootMargin="200px" fallback={<Placeholder />}>
391
429
  <LazyImage />
392
430
  </Visible>
393
431
 
394
- // 切换可见性(once=false 时离开视口会卸载)
432
+ // Toggle visibility (once=false unmounts when leaving viewport)
395
433
  <Visible once={false} onVisibilityChange={(v) => console.log(v)}>
396
434
  <VideoPlayer />
397
435
  </Visible>
398
436
  ```
399
437
 
400
- ## 开发
438
+ ## Development
401
439
 
402
440
  ```bash
403
- # 安装依赖
441
+ # Install dependencies
404
442
  bun install
405
443
 
406
- # 运行测试
444
+ # Run tests
407
445
  bun test
408
446
 
409
- # 代码检查
447
+ # Lint
410
448
  bun run lint
411
449
 
412
- # 构建
450
+ # Build
413
451
  bun run build
414
452
  ```
415
453
 
@@ -1,80 +1,80 @@
1
1
  # react-solidlike
2
2
 
3
- English | [中文](./README.md)
3
+ [English](./README.md) | 中文
4
4
 
5
- Declarative React control flow components inspired by Solid.js. Replaces ternary expressions and `array.map()` in JSX, making your component code cleaner and more readable. Supports React and React Native.
5
+ 声明式 React 控制流组件库,灵感来源于 Solid.js。用于替代 JSX 中的三元表达式和 `array.map()`,让你的组件代码更加清晰易读。支持 React React Native
6
6
 
7
- ## Installation
7
+ ## 安装
8
8
 
9
9
  ```bash
10
10
  npm install react-solidlike
11
- # or
11
+ #
12
12
  bun add react-solidlike
13
13
  ```
14
14
 
15
- ## Components
15
+ ## 组件
16
16
 
17
- ### `<Show>` - Conditional Rendering
17
+ ### `<Show>` - 条件渲染
18
18
 
19
- Replace ternary expressions for conditional rendering.
19
+ 替代三元表达式进行条件渲染。
20
20
 
21
21
  ```tsx
22
22
  import { Show } from "react-solidlike";
23
23
 
24
- // Basic usage
24
+ // 基础用法
25
25
  <Show when={isLoggedIn}>
26
26
  <UserProfile />
27
27
  </Show>
28
28
 
29
- // With fallback
29
+ // fallback
30
30
  <Show when={isLoggedIn} fallback={<LoginButton />}>
31
31
  <UserProfile />
32
32
  </Show>
33
33
 
34
- // Using render props for type-safe value access
34
+ // 使用 render props 获取类型安全的值
35
35
  <Show when={user}>
36
36
  {(user) => <UserProfile name={user.name} />}
37
37
  </Show>
38
38
 
39
- // With onFallback callback (for redirects and other side effects)
39
+ // onFallback 回调(用于重定向等副作用)
40
40
  <Show when={isAuthenticated} fallback={<Loading />} onFallback={() => navigate('/login')}>
41
41
  <Dashboard />
42
42
  </Show>
43
43
  ```
44
44
 
45
- ### `<For>` - List Rendering
45
+ ### `<For>` - 列表渲染
46
46
 
47
- Replace `array.map()` for list rendering.
47
+ 替代 `array.map()` 进行列表渲染。
48
48
 
49
49
  ```tsx
50
50
  import { For } from "react-solidlike";
51
51
 
52
- // Basic usage
52
+ // 基础用法
53
53
  <For each={items}>
54
54
  {(item) => <ListItem {...item} />}
55
55
  </For>
56
56
 
57
- // With keyExtractor
57
+ // keyExtractor
58
58
  <For each={users} keyExtractor={(user) => user.id}>
59
59
  {(user) => <UserCard user={user} />}
60
60
  </For>
61
61
 
62
- // With fallback for empty arrays
62
+ // fallback 处理空数组
63
63
  <For each={items} fallback={<EmptyState />}>
64
64
  {(item, index) => <ListItem item={item} index={index} />}
65
65
  </For>
66
66
 
67
- // With wrapper element
67
+ // 使用 wrapper 包装元素
68
68
  <For each={items} wrapper={<ul className="list" />}>
69
69
  {(item) => <li>{item.name}</li>}
70
70
  </For>
71
71
 
72
- // Reverse rendering
72
+ // 倒序渲染
73
73
  <For each={messages} reverse>
74
74
  {(msg) => <Message {...msg} />}
75
75
  </For>
76
76
 
77
- // Using array parameter for context
77
+ // 使用 array 参数获取上下文信息
78
78
  <For each={steps}>
79
79
  {(step, index, array) => (
80
80
  <Step
@@ -86,9 +86,9 @@ import { For } from "react-solidlike";
86
86
  </For>
87
87
  ```
88
88
 
89
- ### `<Switch>` / `<Match>` / `<Default>` - Multi-branch Rendering
89
+ ### `<Switch>` / `<Match>` / `<Default>` - 多分支渲染
90
90
 
91
- Replace multiple `if-else` or `switch` statements.
91
+ 替代多个 `if-else` `switch` 语句。
92
92
 
93
93
  ```tsx
94
94
  import { Switch, Match, Default } from "react-solidlike";
@@ -109,19 +109,19 @@ import { Switch, Match, Default } from "react-solidlike";
109
109
  </Switch>
110
110
  ```
111
111
 
112
- ### `<Await>` - Async Rendering
112
+ ### `<Await>` - 异步等待
113
113
 
114
- Wait for Promise to resolve before rendering.
114
+ 等待 Promise resolve 后渲染内容。
115
115
 
116
116
  ```tsx
117
117
  import { Await } from "react-solidlike";
118
118
 
119
- // Basic usage
119
+ // 基础用法
120
120
  <Await promise={fetchUser()} loading={<Spinner />}>
121
121
  {(user) => <UserProfile user={user} />}
122
122
  </Await>
123
123
 
124
- // With error handling
124
+ // 带错误处理
125
125
  <Await
126
126
  promise={fetchData()}
127
127
  loading={<Loading />}
@@ -130,40 +130,40 @@ import { Await } from "react-solidlike";
130
130
  {(data) => <DataView data={data} />}
131
131
  </Await>
132
132
 
133
- // Supports non-Promise values (for caching scenarios)
133
+ // 支持非 Promise 值(用于缓存场景)
134
134
  <Await promise={cache ?? fetchData()} loading={<Spinner />}>
135
135
  {(data) => <DataView data={data} />}
136
136
  </Await>
137
137
  ```
138
138
 
139
- ### `<Repeat>` - Repeat Rendering
139
+ ### `<Repeat>` - 重复渲染
140
140
 
141
- Replace `Array.from({ length: n }).map()`.
141
+ 替代 `Array.from({ length: n }).map()`。
142
142
 
143
143
  ```tsx
144
144
  import { Repeat } from "react-solidlike";
145
145
 
146
- // Render star ratings
146
+ // 渲染星级评分
147
147
  <Repeat times={5}>
148
148
  {(i) => <Star key={i} filled={i < rating} />}
149
149
  </Repeat>
150
150
 
151
- // Generate skeleton placeholders
151
+ // 生成骨架屏占位
152
152
  <Repeat times={3}>
153
153
  {(i) => <SkeletonCard key={i} />}
154
154
  </Repeat>
155
155
 
156
- // With wrapper element
156
+ // 使用 wrapper 包装元素
157
157
  <Repeat times={5} wrapper={<div className="stars" />}>
158
158
  {(i) => <Star key={i} />}
159
159
  </Repeat>
160
160
 
161
- // Reverse rendering
161
+ // 倒序渲染
162
162
  <Repeat times={5} reverse>
163
- {(i) => <div key={i}>Reversed {i}</div>}
163
+ {(i) => <div key={i}>倒序 {i}</div>}
164
164
  </Repeat>
165
165
 
166
- // Using length parameter for progress
166
+ // 使用 length 参数显示进度
167
167
  <Repeat times={totalSteps}>
168
168
  {(i, length) => (
169
169
  <Step key={i} current={i + 1} total={length} />
@@ -171,56 +171,56 @@ import { Repeat } from "react-solidlike";
171
171
  </Repeat>
172
172
  ```
173
173
 
174
- ### `<Split>` - String Split Rendering
174
+ ### `<Split>` - 字符串切割渲染
175
175
 
176
- Split a string by separator and render each part.
176
+ 按分隔符切割字符串并渲染每个部分。
177
177
 
178
178
  ```tsx
179
179
  import { Split } from "react-solidlike";
180
180
 
181
- // Basic usage - splits without keeping separator
181
+ // 基础用法 - 切割后不保留分隔符
182
182
  <Split string="a,b,c" separator=",">
183
183
  {(part) => <span>{part}</span>}
184
184
  </Split>
185
- // Renders: ["a", "b", "c"]
185
+ // 渲染: ["a", "b", "c"]
186
186
 
187
- // Keep separator in result
187
+ // 保留分隔符
188
188
  <Split string="9+5=(9+1)+4" separator="=" keepSeparator>
189
189
  {(part) => <span>{part}</span>}
190
190
  </Split>
191
- // Renders: ["9+5", "=", "(9+1)+4"]
191
+ // 渲染: ["9+5", "=", "(9+1)+4"]
192
192
 
193
- // Using RegExp separator
193
+ // 使用正则表达式分隔符
194
194
  <Split string="a1b2c3" separator={/\d/} keepSeparator>
195
195
  {(part) => <span>{part}</span>}
196
196
  </Split>
197
- // Renders: ["a", "1", "b", "2", "c", "3"]
197
+ // 渲染: ["a", "1", "b", "2", "c", "3"]
198
198
 
199
- // With wrapper element
199
+ // wrapper 包装元素
200
200
  <Split string="hello world" separator=" " wrapper={<div className="words" />}>
201
201
  {(word) => <span>{word}</span>}
202
202
  </Split>
203
203
 
204
- // With fallback for empty string
204
+ // fallback 处理空字符串
205
205
  <Split string={text} separator="," fallback={<EmptyState />}>
206
206
  {(part) => <Tag>{part}</Tag>}
207
207
  </Split>
208
208
 
209
- // Reverse rendering
209
+ // 倒序渲染
210
210
  <Split string="a,b,c" separator="," reverse>
211
211
  {(part) => <span>{part}</span>}
212
212
  </Split>
213
- // Render order: ["c", "b", "a"]
213
+ // 渲染顺序: ["c", "b", "a"]
214
214
  ```
215
215
 
216
- ### `<Dynamic>` - Dynamic Component
216
+ ### `<Dynamic>` - 动态组件
217
217
 
218
- Dynamically select component type based on conditions.
218
+ 根据条件动态选择要渲染的组件类型。
219
219
 
220
220
  ```tsx
221
221
  import { Dynamic } from "react-solidlike";
222
222
 
223
- // Dynamic button or link
223
+ // 动态选择按钮或链接
224
224
  <Dynamic
225
225
  component={href ? 'a' : 'button'}
226
226
  href={href}
@@ -229,13 +229,13 @@ import { Dynamic } from "react-solidlike";
229
229
  {label}
230
230
  </Dynamic>
231
231
 
232
- // With custom components
232
+ // 配合自定义组件
233
233
  <Dynamic
234
234
  component={isAdmin ? AdminPanel : UserPanel}
235
235
  user={currentUser}
236
236
  />
237
237
 
238
- // React Native usage
238
+ // React Native 中使用
239
239
  <Dynamic
240
240
  component={isPressable ? Pressable : View}
241
241
  onPress={handlePress}
@@ -244,19 +244,19 @@ import { Dynamic } from "react-solidlike";
244
244
  </Dynamic>
245
245
  ```
246
246
 
247
- ### `<ErrorBoundary>` - Error Boundary
247
+ ### `<ErrorBoundary>` - 错误边界
248
248
 
249
- Catch JavaScript errors in child component tree.
249
+ 捕获子组件树中的 JavaScript 错误,防止整个应用崩溃。
250
250
 
251
251
  ```tsx
252
252
  import { ErrorBoundary } from "react-solidlike";
253
253
 
254
- // Basic usage
254
+ // 基础用法
255
255
  <ErrorBoundary fallback={<ErrorPage />}>
256
256
  <App />
257
257
  </ErrorBoundary>
258
258
 
259
- // With render props for error info and reset function
259
+ // 使用 render props 获取错误信息和重置函数
260
260
  <ErrorBoundary
261
261
  fallback={(error, reset) => (
262
262
  <div>
@@ -268,15 +268,15 @@ import { ErrorBoundary } from "react-solidlike";
268
268
  <App />
269
269
  </ErrorBoundary>
270
270
 
271
- // Auto-reset when resetKey changes
271
+ // resetKey 变化时自动重置
272
272
  <ErrorBoundary fallback={<Error />} resetKey={userId}>
273
273
  <UserProfile />
274
274
  </ErrorBoundary>
275
275
  ```
276
276
 
277
- ### `<QueryBoundary>` - Query Boundary
277
+ ### `<QueryBoundary>` - 查询边界
278
278
 
279
- Handle async query states (loading, error, empty, success). Works with `@tanstack/react-query`, SWR, RTK Query, etc.
279
+ 处理异步查询的各种状态(加载中、错误、空数据、成功)。可与 `@tanstack/react-query`、SWRRTK Query 等配合使用。
280
280
 
281
281
  ```tsx
282
282
  import { QueryBoundary } from "react-solidlike";
@@ -306,28 +306,28 @@ function UserList() {
306
306
 
307
307
  #### Props
308
308
 
309
- | Prop | Type | Description |
310
- |------|------|-------------|
311
- | `query` | `QueryResult<T>` | Query result object |
312
- | `loading` | `ReactNode` | Loading state content |
313
- | `error` | `ReactNode` | Error state content |
314
- | `empty` | `ReactNode` | Empty state content |
315
- | `children` | `ReactNode \| (data: T) => ReactNode` | Success content |
316
- | `isEmptyFn` | `(data: T) => boolean` | Custom empty check |
309
+ | 属性 | 类型 | 描述 |
310
+ | ----------- | ------------------------------------- | ------------ |
311
+ | `query` | `QueryResult<T>` | 查询结果对象 |
312
+ | `loading` | `ReactNode` | 加载中显示 |
313
+ | `error` | `ReactNode` | 错误时显示 |
314
+ | `empty` | `ReactNode` | 空数据显示 |
315
+ | `children` | `ReactNode \| (data: T) => ReactNode` | 成功时渲染 |
316
+ | `isEmptyFn` | `(data: T) => boolean` | 自定义空判断 |
317
317
 
318
- ### `<Once>` - Single Render
318
+ ### `<Once>` - 单次渲染
319
319
 
320
- Renders children only once and ignores subsequent updates. Useful for expensive computations or content that shouldn't re-render.
320
+ 只渲染一次子元素,忽略后续更新。适用于昂贵的计算或不应重新渲染的内容。
321
321
 
322
322
  ```tsx
323
323
  import { Once } from "react-solidlike";
324
324
 
325
- // Render expensive component once
325
+ // 渲染昂贵的组件
326
326
  <Once>
327
327
  <ExpensiveChart data={data} />
328
328
  </Once>
329
329
 
330
- // Prevent re-renders from parent updates
330
+ // 防止父组件更新导致的重新渲染
331
331
  function Parent() {
332
332
  const [count, setCount] = useState(0);
333
333
  return (
@@ -341,75 +341,113 @@ function Parent() {
341
341
  }
342
342
  ```
343
343
 
344
- ### `<ClientOnly>` - Client-side Only Rendering
344
+ ### `<ClientOnly>` - 仅客户端渲染
345
345
 
346
- Renders children only on the client side (after hydration). Useful for components that rely on browser APIs or need to avoid SSR hydration mismatches.
346
+ 仅在客户端(hydration 之后)渲染子元素。适用于依赖浏览器 API 或需要避免 SSR hydration 不匹配的场景。
347
347
 
348
348
  ```tsx
349
349
  import { ClientOnly } from "react-solidlike";
350
350
 
351
- // Basic usage
351
+ // 基础用法
352
352
  <ClientOnly>
353
353
  <BrowserOnlyComponent />
354
354
  </ClientOnly>
355
355
 
356
- // With SSR fallback
356
+ // SSR 备选内容
357
357
  <ClientOnly fallback={<Skeleton />}>
358
358
  <DynamicChart />
359
359
  </ClientOnly>
360
360
 
361
- // Using render function for lazy evaluation (avoid accessing window)
361
+ // 使用渲染函数延迟求值(避免访问 window
362
362
  <ClientOnly fallback={<Loading />}>
363
363
  {() => <ComponentUsingWindow width={window.innerWidth} />}
364
364
  </ClientOnly>
365
365
 
366
- // Avoid hydration mismatch
366
+ // 避免 hydration 不匹配
367
367
  <ClientOnly fallback={<span>--:--</span>}>
368
368
  <CurrentTime />
369
369
  </ClientOnly>
370
370
  ```
371
371
 
372
- ### `<Visible>` - Visibility-based Rendering (Web only)
372
+ ### `<Timeout>` - 超时渲染
373
373
 
374
- Renders children when entering viewport using IntersectionObserver. In React Native or unsupported environments, children are rendered directly (graceful degradation).
374
+ 在指定延迟后显示或隐藏内容。适用于自动消失的通知、延迟加载的场景。
375
+
376
+ ```tsx
377
+ import { Timeout } from "react-solidlike";
378
+
379
+ // 延迟后显示(mode="after",默认)
380
+ <Timeout ms={1000} mode="after" fallback={<Spinner />}>
381
+ <DelayedContent />
382
+ </Timeout>
383
+
384
+ // 延迟后隐藏(mode="before")
385
+ <Timeout ms={3000} mode="before">
386
+ <Toast message="操作成功!" />
387
+ </Timeout>
388
+
389
+ // 自动消失的提示
390
+ <Timeout ms={5000} mode="before" onTimeout={() => console.log("已消失")}>
391
+ <Notification type="success">保存成功</Notification>
392
+ </Timeout>
393
+
394
+ // 带加载状态的延迟渲染
395
+ <Timeout ms={2000} mode="after" fallback={<Skeleton />}>
396
+ <ExpensiveComponent />
397
+ </Timeout>
398
+ ```
399
+
400
+ #### Props
401
+
402
+ | 属性 | 类型 | 描述 |
403
+ | ----------- | --------------------- | --------------------------------------------------------------- |
404
+ | `ms` | `number` | 延迟时间(毫秒) |
405
+ | `mode` | `'after' \| 'before'` | `'after'` = 延迟后显示,`'before'` = 延迟后隐藏,默认 `'after'` |
406
+ | `children` | `ReactNode` | 要渲染的内容 |
407
+ | `fallback` | `ReactNode` | 等待时显示的内容(仅 `after` 模式) |
408
+ | `onTimeout` | `() => void` | 超时发生时的回调 |
409
+
410
+ ### `<Visible>` - 可见性渲染(仅 Web)
411
+
412
+ 基于 IntersectionObserver 的可见性渲染,进入视口才渲���。在 React Native 或不支持的环境中会直接渲染 children(优雅降级)。
375
413
 
376
414
  ```tsx
377
415
  import { Visible } from "react-solidlike";
378
416
 
379
- // Basic usage - render when entering viewport
417
+ // 基础用法 - 进入视口时渲染
380
418
  <Visible>
381
419
  <HeavyComponent />
382
420
  </Visible>
383
421
 
384
- // With placeholder
422
+ // 带占位符
385
423
  <Visible fallback={<Skeleton />}>
386
424
  <Image src={url} />
387
425
  </Visible>
388
426
 
389
- // Preload before entering viewport (rootMargin)
427
+ // 提前预加载(rootMargin
390
428
  <Visible rootMargin="200px" fallback={<Placeholder />}>
391
429
  <LazyImage />
392
430
  </Visible>
393
431
 
394
- // Toggle visibility (once=false unmounts when leaving viewport)
432
+ // 切换可见性(once=false 时离开视口会卸载)
395
433
  <Visible once={false} onVisibilityChange={(v) => console.log(v)}>
396
434
  <VideoPlayer />
397
435
  </Visible>
398
436
  ```
399
437
 
400
- ## Development
438
+ ## 开发
401
439
 
402
440
  ```bash
403
- # Install dependencies
441
+ # 安装依赖
404
442
  bun install
405
443
 
406
- # Run tests
444
+ # 运行测试
407
445
  bun test
408
446
 
409
- # Lint
447
+ # 代码检查
410
448
  bun run lint
411
449
 
412
- # Build
450
+ # 构建
413
451
  bun run build
414
452
  ```
415
453
 
@@ -1,4 +1,4 @@
1
- import { type ReactNode } from 'react';
1
+ import { type ReactNode } from "react";
2
2
  export interface ClientOnlyProps {
3
3
  /** Content to render only on client side | 仅在客户端渲染的内容 */
4
4
  children: ReactNode | (() => ReactNode);
package/dist/Repeat.d.ts CHANGED
@@ -1,6 +1,6 @@
1
- import type { ReactNode } from 'react';
2
- import { type ForProps } from './For';
3
- export interface RepeatProps extends Omit<ForProps<number>, 'each'> {
1
+ import type { ReactNode } from "react";
2
+ import { type ForProps } from "./For";
3
+ export interface RepeatProps extends Omit<ForProps<number>, "each"> {
4
4
  /** Number of times to repeat | 重复次数 */
5
5
  times: number;
6
6
  }
package/dist/Split.d.ts CHANGED
@@ -1,6 +1,6 @@
1
- import type { ReactNode } from 'react';
2
- import { type ForProps } from './For';
3
- export interface SplitProps extends Omit<ForProps<string>, 'each'> {
1
+ import type { ReactNode } from "react";
2
+ import { type ForProps } from "./For";
3
+ export interface SplitProps extends Omit<ForProps<string>, "each"> {
4
4
  /** String to split | 要切割的字符串 */
5
5
  string: string | null | undefined;
6
6
  /** Separator to split by, can be string or RegExp | 分隔符,可以是字符串或正则表达式 */
@@ -0,0 +1,45 @@
1
+ import { type ReactNode } from "react";
2
+ /** Timeout mode | 超时模式 */
3
+ export type TimeoutMode = "after" | "before";
4
+ export interface TimeoutProps {
5
+ /** Delay time in milliseconds | 延迟时间(毫秒) */
6
+ ms: number;
7
+ /** Content to render | 要渲染的内容 */
8
+ children: ReactNode;
9
+ /** Display mode: 'after' = show after delay, 'before' = hide after delay | 显示模式:'after' = 延迟后显示,'before' = 延迟后隐藏 */
10
+ mode?: TimeoutMode;
11
+ /** Content to show when hidden (only for 'after' mode) | 隐藏时显示的内容(仅 'after' 模式) */
12
+ fallback?: ReactNode;
13
+ /** Callback when timeout occurs | 超时发生时的回调 */
14
+ onTimeout?: () => void;
15
+ }
16
+ /**
17
+ * Timeout component, shows or hides content after a specified delay
18
+ *
19
+ * 超时组件,在指定延迟后显示或隐藏内容
20
+ *
21
+ * @example
22
+ * // Show content after delay (fade-in effect) | 延迟后显示内容(淡入效果)
23
+ * <Timeout ms={1000} mode="after">
24
+ * <DelayedMessage />
25
+ * </Timeout>
26
+ *
27
+ * @example
28
+ * // Hide content after delay (auto-dismiss) | 延迟后隐藏内容(自动消失)
29
+ * <Timeout ms={3000} mode="before">
30
+ * <Toast message="Saved successfully!" />
31
+ * </Timeout>
32
+ *
33
+ * @example
34
+ * // With fallback while waiting | 等待时显示 fallback
35
+ * <Timeout ms={2000} mode="after" fallback={<Spinner />}>
36
+ * <SlowComponent />
37
+ * </Timeout>
38
+ *
39
+ * @example
40
+ * // With onTimeout callback | 带超时回调
41
+ * <Timeout ms={5000} mode="before" onTimeout={() => console.log("Dismissed")}>
42
+ * <Notification />
43
+ * </Timeout>
44
+ */
45
+ export declare function Timeout({ ms, children, mode, fallback, onTimeout }: TimeoutProps): ReactNode;
package/dist/index.d.ts CHANGED
@@ -1,12 +1,11 @@
1
1
  export { Await, type AwaitProps } from "./Await";
2
- export { ClientOnly, type ClientOnlyProps } from "./ClientOnly";
3
2
  export { Dynamic, type DynamicProps } from "./Dynamic";
4
3
  export { ErrorBoundary, type ErrorBoundaryProps } from "./ErrorBoundary";
5
4
  export { For, type ForProps } from "./For";
6
- export { Once, type OnceProps } from "./Once";
7
5
  export { QueryBoundary, type QueryBoundaryProps, type QueryResult } from "./QueryBoundary";
8
6
  export { Repeat, type RepeatProps } from "./Repeat";
9
7
  export { Show, type ShowProps } from "./Show";
10
8
  export { Split, type SplitProps } from "./Split";
11
- export { Default, type DefaultProps, Match, type MatchProps, Switch, type SwitchProps } from "./Switch";
9
+ export { Default, type DefaultProps, Match, type MatchProps, Switch, type SwitchProps, } from "./Switch";
10
+ export { Timeout, type TimeoutProps } from "./Timeout";
12
11
  export { Visible, type VisibleProps } from "./Visible";
package/dist/index.js CHANGED
@@ -43,16 +43,6 @@ function Await({ promise, loading = null, error = null, children }) {
43
43
  return children;
44
44
  }
45
45
 
46
- function ClientOnly({ children, fallback = null }) {
47
- const [isClient, setIsClient] = useState(false);
48
- useEffect(() => {
49
- setIsClient(true);
50
- }, []);
51
- if (!isClient) return fallback;
52
- if (typeof children === "function") return children();
53
- return children;
54
- }
55
-
56
46
  function Dynamic({ component, fallback = null, ...props }) {
57
47
  if (!component) return fallback;
58
48
  return createElement(component, props);
@@ -90,24 +80,14 @@ function For({ each, children, keyExtractor, fallback = null, wrapper, reverse }
90
80
  if (!each || each.length === 0) return fallback;
91
81
  const elements = (reverse ? [...each].reverse() : each).map((item, i) => {
92
82
  const originalIndex = reverse ? each.length - 1 - i : i;
93
- const key = keyExtractor ? keyExtractor(item, originalIndex) : originalIndex;
94
- return /* @__PURE__ */ jsx(Fragment, { children: children(item, originalIndex, each) }, key);
83
+ const child = children(item, originalIndex, each);
84
+ const childKey = isValidElement(child) ? child.key : null;
85
+ const key = keyExtractor ? keyExtractor(item, originalIndex) : childKey ?? originalIndex;
86
+ return /* @__PURE__ */ jsx(Fragment, { children: child }, key);
95
87
  });
96
88
  return wrapper && isValidElement(wrapper) ? cloneElement(wrapper, {}, elements) : elements;
97
89
  }
98
90
 
99
- function Once({ children }) {
100
- const cachedRef = useRef({
101
- rendered: false,
102
- content: null
103
- });
104
- if (!cachedRef.current.rendered) {
105
- cachedRef.current.rendered = true;
106
- cachedRef.current.content = children;
107
- }
108
- return cachedRef.current.content;
109
- }
110
-
111
91
  function defaultIsEmpty(data) {
112
92
  if (data == null) return true;
113
93
  if (Array.isArray(data)) return data.length === 0;
@@ -206,6 +186,19 @@ function Switch({ children, fallback = null }) {
206
186
  return defaultContent;
207
187
  }
208
188
 
189
+ function Timeout({ ms, children, mode = "after", fallback = null, onTimeout }) {
190
+ const [ready, setReady] = useState(mode === "before");
191
+ useEffect(() => {
192
+ const timer = setTimeout(() => {
193
+ setReady((prev) => !prev);
194
+ onTimeout?.();
195
+ }, ms);
196
+ return () => clearTimeout(timer);
197
+ }, [ms, onTimeout]);
198
+ if (mode === "after") return ready ? children : fallback;
199
+ return ready ? children : null;
200
+ }
201
+
209
202
  function Visible({ children, fallback = null, rootMargin = "0px", threshold = 0, once = true, onVisibilityChange }) {
210
203
  const [isVisible, setIsVisible] = useState(false);
211
204
  const [hasBeenVisible, setHasBeenVisible] = useState(false);
@@ -257,4 +250,4 @@ function Visible({ children, fallback = null, rootMargin = "0px", threshold = 0,
257
250
  });
258
251
  }
259
252
 
260
- export { Await, ClientOnly, Default, Dynamic, ErrorBoundary, For, Match, Once, QueryBoundary, Repeat, Show, Split, Switch, Visible };
253
+ export { Await, Default, Dynamic, ErrorBoundary, For, Match, QueryBoundary, Repeat, Show, Split, Switch, Timeout, Visible };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-solidlike",
3
- "version": "2.4.0",
3
+ "version": "2.5.1",
4
4
  "description": "Declarative React control flow components inspired by Solid.js, replacing ternary expressions and array.map() in JSX",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -18,11 +18,12 @@
18
18
  "scripts": {
19
19
  "build": "rm -rf dist && rolldown -c && tsc -p tsconfig.build.json",
20
20
  "test": "bun test",
21
- "test:e2e": "playwright test",
21
+ "test:e2e": "playwright test && playwright show-report",
22
+ "pretest:e2e": "bun e2e/check-browsers.ts",
22
23
  "lint": "biome check .",
23
24
  "lint:fix": "biome check --write .",
24
25
  "format": "biome format --write .",
25
- "prepublishOnly": "bun test && bun run build"
26
+ "prepublishOnly": "bun format && bun test && bun run build"
26
27
  },
27
28
  "keywords": [
28
29
  "react",