react-solidlike 2.4.0 → 2.5.0
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 +125 -87
- package/{README.en.md → README.zh.md} +125 -87
- package/dist/ClientOnly.d.ts +1 -1
- package/dist/Repeat.d.ts +3 -3
- package/dist/Split.d.ts +3 -3
- package/dist/Timeout.d.ts +45 -0
- package/dist/index.d.ts +2 -3
- package/dist/index.js +14 -23
- package/package.json +4 -3
package/README.md
CHANGED
|
@@ -1,80 +1,80 @@
|
|
|
1
1
|
# react-solidlike
|
|
2
2
|
|
|
3
|
-
[
|
|
3
|
+
English | [中文](./README.zh.md)
|
|
4
4
|
|
|
5
|
-
|
|
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
|
-
//
|
|
29
|
+
// With fallback
|
|
30
30
|
<Show when={isLoggedIn} fallback={<LoginButton />}>
|
|
31
31
|
<UserProfile />
|
|
32
32
|
</Show>
|
|
33
33
|
|
|
34
|
-
//
|
|
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
|
-
//
|
|
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
|
-
|
|
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
|
-
//
|
|
57
|
+
// With keyExtractor
|
|
58
58
|
<For each={users} keyExtractor={(user) => user.id}>
|
|
59
59
|
{(user) => <UserCard user={user} />}
|
|
60
60
|
</For>
|
|
61
61
|
|
|
62
|
-
//
|
|
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
|
-
//
|
|
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
|
-
//
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
//
|
|
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
|
-
|
|
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
|
-
//
|
|
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}
|
|
163
|
+
{(i) => <div key={i}>Reversed {i}</div>}
|
|
164
164
|
</Repeat>
|
|
165
165
|
|
|
166
|
-
//
|
|
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
|
-
//
|
|
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
|
-
//
|
|
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
|
-
//
|
|
197
|
+
// Renders: ["a", "1", "b", "2", "c", "3"]
|
|
198
198
|
|
|
199
|
-
//
|
|
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
|
-
//
|
|
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
|
-
//
|
|
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
|
-
|
|
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
|
-
//
|
|
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
|
-
|
|
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`
|
|
312
|
-
| `loading`
|
|
313
|
-
| `error`
|
|
314
|
-
| `empty`
|
|
315
|
-
| `children`
|
|
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
|
-
|
|
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
|
-
//
|
|
356
|
+
// With SSR fallback
|
|
357
357
|
<ClientOnly fallback={<Skeleton />}>
|
|
358
358
|
<DynamicChart />
|
|
359
359
|
</ClientOnly>
|
|
360
360
|
|
|
361
|
-
//
|
|
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
|
-
//
|
|
366
|
+
// Avoid hydration mismatch
|
|
367
367
|
<ClientOnly fallback={<span>--:--</span>}>
|
|
368
368
|
<CurrentTime />
|
|
369
369
|
</ClientOnly>
|
|
370
370
|
```
|
|
371
371
|
|
|
372
|
-
### `<
|
|
372
|
+
### `<Timeout>` - Timeout Rendering
|
|
373
373
|
|
|
374
|
-
|
|
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
|
-
//
|
|
427
|
+
// Preload before entering viewport (rootMargin)
|
|
390
428
|
<Visible rootMargin="200px" fallback={<Placeholder />}>
|
|
391
429
|
<LazyImage />
|
|
392
430
|
</Visible>
|
|
393
431
|
|
|
394
|
-
//
|
|
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
|
|
3
|
+
[English](./README.md) | 中文
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
声明式 React 控制流组件库,灵感来源于 Solid.js。用于替代 JSX 中的三元表达式和 `array.map()`,让你的组件代码更加清晰易读。支持 React 和 React Native。
|
|
6
6
|
|
|
7
|
-
##
|
|
7
|
+
## 安装
|
|
8
8
|
|
|
9
9
|
```bash
|
|
10
10
|
npm install react-solidlike
|
|
11
|
-
#
|
|
11
|
+
# 或
|
|
12
12
|
bun add react-solidlike
|
|
13
13
|
```
|
|
14
14
|
|
|
15
|
-
##
|
|
15
|
+
## 组件
|
|
16
16
|
|
|
17
|
-
### `<Show>` -
|
|
17
|
+
### `<Show>` - 条件渲染
|
|
18
18
|
|
|
19
|
-
|
|
19
|
+
替代三元表达式进行条件渲染。
|
|
20
20
|
|
|
21
21
|
```tsx
|
|
22
22
|
import { Show } from "react-solidlike";
|
|
23
23
|
|
|
24
|
-
//
|
|
24
|
+
// 基础用法
|
|
25
25
|
<Show when={isLoggedIn}>
|
|
26
26
|
<UserProfile />
|
|
27
27
|
</Show>
|
|
28
28
|
|
|
29
|
-
//
|
|
29
|
+
// 带 fallback
|
|
30
30
|
<Show when={isLoggedIn} fallback={<LoginButton />}>
|
|
31
31
|
<UserProfile />
|
|
32
32
|
</Show>
|
|
33
33
|
|
|
34
|
-
//
|
|
34
|
+
// 使用 render props 获取类型安全的值
|
|
35
35
|
<Show when={user}>
|
|
36
36
|
{(user) => <UserProfile name={user.name} />}
|
|
37
37
|
</Show>
|
|
38
38
|
|
|
39
|
-
//
|
|
39
|
+
// 带 onFallback 回调(用于重定向等副作用)
|
|
40
40
|
<Show when={isAuthenticated} fallback={<Loading />} onFallback={() => navigate('/login')}>
|
|
41
41
|
<Dashboard />
|
|
42
42
|
</Show>
|
|
43
43
|
```
|
|
44
44
|
|
|
45
|
-
### `<For>` -
|
|
45
|
+
### `<For>` - 列表渲染
|
|
46
46
|
|
|
47
|
-
|
|
47
|
+
替代 `array.map()` 进行列表渲染。
|
|
48
48
|
|
|
49
49
|
```tsx
|
|
50
50
|
import { For } from "react-solidlike";
|
|
51
51
|
|
|
52
|
-
//
|
|
52
|
+
// 基础用法
|
|
53
53
|
<For each={items}>
|
|
54
54
|
{(item) => <ListItem {...item} />}
|
|
55
55
|
</For>
|
|
56
56
|
|
|
57
|
-
//
|
|
57
|
+
// 带 keyExtractor
|
|
58
58
|
<For each={users} keyExtractor={(user) => user.id}>
|
|
59
59
|
{(user) => <UserCard user={user} />}
|
|
60
60
|
</For>
|
|
61
61
|
|
|
62
|
-
//
|
|
62
|
+
// 带 fallback 处理空数组
|
|
63
63
|
<For each={items} fallback={<EmptyState />}>
|
|
64
64
|
{(item, index) => <ListItem item={item} index={index} />}
|
|
65
65
|
</For>
|
|
66
66
|
|
|
67
|
-
//
|
|
67
|
+
// 使用 wrapper 包装元素
|
|
68
68
|
<For each={items} wrapper={<ul className="list" />}>
|
|
69
69
|
{(item) => <li>{item.name}</li>}
|
|
70
70
|
</For>
|
|
71
71
|
|
|
72
|
-
//
|
|
72
|
+
// 倒序渲染
|
|
73
73
|
<For each={messages} reverse>
|
|
74
74
|
{(msg) => <Message {...msg} />}
|
|
75
75
|
</For>
|
|
76
76
|
|
|
77
|
-
//
|
|
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>` -
|
|
89
|
+
### `<Switch>` / `<Match>` / `<Default>` - 多分支渲染
|
|
90
90
|
|
|
91
|
-
|
|
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>` -
|
|
112
|
+
### `<Await>` - 异步等待
|
|
113
113
|
|
|
114
|
-
|
|
114
|
+
等待 Promise resolve 后渲染内容。
|
|
115
115
|
|
|
116
116
|
```tsx
|
|
117
117
|
import { Await } from "react-solidlike";
|
|
118
118
|
|
|
119
|
-
//
|
|
119
|
+
// 基础用法
|
|
120
120
|
<Await promise={fetchUser()} loading={<Spinner />}>
|
|
121
121
|
{(user) => <UserProfile user={user} />}
|
|
122
122
|
</Await>
|
|
123
123
|
|
|
124
|
-
//
|
|
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
|
-
//
|
|
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>` -
|
|
139
|
+
### `<Repeat>` - 重复渲染
|
|
140
140
|
|
|
141
|
-
|
|
141
|
+
替代 `Array.from({ length: n }).map()`。
|
|
142
142
|
|
|
143
143
|
```tsx
|
|
144
144
|
import { Repeat } from "react-solidlike";
|
|
145
145
|
|
|
146
|
-
//
|
|
146
|
+
// 渲染星级评分
|
|
147
147
|
<Repeat times={5}>
|
|
148
148
|
{(i) => <Star key={i} filled={i < rating} />}
|
|
149
149
|
</Repeat>
|
|
150
150
|
|
|
151
|
-
//
|
|
151
|
+
// 生成骨架屏占位
|
|
152
152
|
<Repeat times={3}>
|
|
153
153
|
{(i) => <SkeletonCard key={i} />}
|
|
154
154
|
</Repeat>
|
|
155
155
|
|
|
156
|
-
//
|
|
156
|
+
// 使用 wrapper 包装元素
|
|
157
157
|
<Repeat times={5} wrapper={<div className="stars" />}>
|
|
158
158
|
{(i) => <Star key={i} />}
|
|
159
159
|
</Repeat>
|
|
160
160
|
|
|
161
|
-
//
|
|
161
|
+
// 倒序渲染
|
|
162
162
|
<Repeat times={5} reverse>
|
|
163
|
-
{(i) => <div key={i}
|
|
163
|
+
{(i) => <div key={i}>倒序 {i}</div>}
|
|
164
164
|
</Repeat>
|
|
165
165
|
|
|
166
|
-
//
|
|
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>` -
|
|
174
|
+
### `<Split>` - 字符串切割渲染
|
|
175
175
|
|
|
176
|
-
|
|
176
|
+
按分隔符切割字符串并渲染每个部分。
|
|
177
177
|
|
|
178
178
|
```tsx
|
|
179
179
|
import { Split } from "react-solidlike";
|
|
180
180
|
|
|
181
|
-
//
|
|
181
|
+
// 基础用法 - 切割后不保留分隔符
|
|
182
182
|
<Split string="a,b,c" separator=",">
|
|
183
183
|
{(part) => <span>{part}</span>}
|
|
184
184
|
</Split>
|
|
185
|
-
//
|
|
185
|
+
// 渲染: ["a", "b", "c"]
|
|
186
186
|
|
|
187
|
-
//
|
|
187
|
+
// 保留分隔符
|
|
188
188
|
<Split string="9+5=(9+1)+4" separator="=" keepSeparator>
|
|
189
189
|
{(part) => <span>{part}</span>}
|
|
190
190
|
</Split>
|
|
191
|
-
//
|
|
191
|
+
// 渲染: ["9+5", "=", "(9+1)+4"]
|
|
192
192
|
|
|
193
|
-
//
|
|
193
|
+
// 使用正则表达式分隔符
|
|
194
194
|
<Split string="a1b2c3" separator={/\d/} keepSeparator>
|
|
195
195
|
{(part) => <span>{part}</span>}
|
|
196
196
|
</Split>
|
|
197
|
-
//
|
|
197
|
+
// 渲染: ["a", "1", "b", "2", "c", "3"]
|
|
198
198
|
|
|
199
|
-
//
|
|
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
|
-
//
|
|
204
|
+
// 带 fallback 处理空字符串
|
|
205
205
|
<Split string={text} separator="," fallback={<EmptyState />}>
|
|
206
206
|
{(part) => <Tag>{part}</Tag>}
|
|
207
207
|
</Split>
|
|
208
208
|
|
|
209
|
-
//
|
|
209
|
+
// 倒序渲染
|
|
210
210
|
<Split string="a,b,c" separator="," reverse>
|
|
211
211
|
{(part) => <span>{part}</span>}
|
|
212
212
|
</Split>
|
|
213
|
-
//
|
|
213
|
+
// 渲染顺序: ["c", "b", "a"]
|
|
214
214
|
```
|
|
215
215
|
|
|
216
|
-
### `<Dynamic>` -
|
|
216
|
+
### `<Dynamic>` - 动态组件
|
|
217
217
|
|
|
218
|
-
|
|
218
|
+
根据条件动态选择要渲染的组件类型。
|
|
219
219
|
|
|
220
220
|
```tsx
|
|
221
221
|
import { Dynamic } from "react-solidlike";
|
|
222
222
|
|
|
223
|
-
//
|
|
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
|
-
//
|
|
232
|
+
// 配合自定义组件
|
|
233
233
|
<Dynamic
|
|
234
234
|
component={isAdmin ? AdminPanel : UserPanel}
|
|
235
235
|
user={currentUser}
|
|
236
236
|
/>
|
|
237
237
|
|
|
238
|
-
// React Native
|
|
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>` -
|
|
247
|
+
### `<ErrorBoundary>` - 错误边界
|
|
248
248
|
|
|
249
|
-
|
|
249
|
+
捕获子组件树中的 JavaScript 错误,防止整个应用崩溃。
|
|
250
250
|
|
|
251
251
|
```tsx
|
|
252
252
|
import { ErrorBoundary } from "react-solidlike";
|
|
253
253
|
|
|
254
|
-
//
|
|
254
|
+
// 基础用法
|
|
255
255
|
<ErrorBoundary fallback={<ErrorPage />}>
|
|
256
256
|
<App />
|
|
257
257
|
</ErrorBoundary>
|
|
258
258
|
|
|
259
|
-
//
|
|
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
|
-
//
|
|
271
|
+
// resetKey 变化时自动重置
|
|
272
272
|
<ErrorBoundary fallback={<Error />} resetKey={userId}>
|
|
273
273
|
<UserProfile />
|
|
274
274
|
</ErrorBoundary>
|
|
275
275
|
```
|
|
276
276
|
|
|
277
|
-
### `<QueryBoundary>` -
|
|
277
|
+
### `<QueryBoundary>` - 查询边界
|
|
278
278
|
|
|
279
|
-
|
|
279
|
+
处理异步查询的各种状态(加载中、错误、空数据、成功)。可与 `@tanstack/react-query`、SWR、RTK 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
|
-
|
|
|
310
|
-
|
|
311
|
-
| `query`
|
|
312
|
-
| `loading`
|
|
313
|
-
| `error`
|
|
314
|
-
| `empty`
|
|
315
|
-
| `children`
|
|
316
|
-
| `isEmptyFn` | `(data: T) => boolean`
|
|
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>` -
|
|
318
|
+
### `<Once>` - 单次渲染
|
|
319
319
|
|
|
320
|
-
|
|
320
|
+
只渲染一次子元素,忽略后续更新。适用于昂贵的计算或不应重新渲染的内容。
|
|
321
321
|
|
|
322
322
|
```tsx
|
|
323
323
|
import { Once } from "react-solidlike";
|
|
324
324
|
|
|
325
|
-
//
|
|
325
|
+
// 渲染昂贵的组件
|
|
326
326
|
<Once>
|
|
327
327
|
<ExpensiveChart data={data} />
|
|
328
328
|
</Once>
|
|
329
329
|
|
|
330
|
-
//
|
|
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>` -
|
|
344
|
+
### `<ClientOnly>` - 仅客户端渲染
|
|
345
345
|
|
|
346
|
-
|
|
346
|
+
仅在客户端(hydration 之后)渲染子元素。适用于依赖浏览器 API 或需要避免 SSR hydration 不匹配的场景。
|
|
347
347
|
|
|
348
348
|
```tsx
|
|
349
349
|
import { ClientOnly } from "react-solidlike";
|
|
350
350
|
|
|
351
|
-
//
|
|
351
|
+
// 基础用法
|
|
352
352
|
<ClientOnly>
|
|
353
353
|
<BrowserOnlyComponent />
|
|
354
354
|
</ClientOnly>
|
|
355
355
|
|
|
356
|
-
//
|
|
356
|
+
// 带 SSR 备选内容
|
|
357
357
|
<ClientOnly fallback={<Skeleton />}>
|
|
358
358
|
<DynamicChart />
|
|
359
359
|
</ClientOnly>
|
|
360
360
|
|
|
361
|
-
//
|
|
361
|
+
// 使用渲染函数延迟求值(避免访问 window)
|
|
362
362
|
<ClientOnly fallback={<Loading />}>
|
|
363
363
|
{() => <ComponentUsingWindow width={window.innerWidth} />}
|
|
364
364
|
</ClientOnly>
|
|
365
365
|
|
|
366
|
-
//
|
|
366
|
+
// 避免 hydration 不匹配
|
|
367
367
|
<ClientOnly fallback={<span>--:--</span>}>
|
|
368
368
|
<CurrentTime />
|
|
369
369
|
</ClientOnly>
|
|
370
370
|
```
|
|
371
371
|
|
|
372
|
-
### `<
|
|
372
|
+
### `<Timeout>` - 超时渲染
|
|
373
373
|
|
|
374
|
-
|
|
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
|
-
//
|
|
417
|
+
// 基础用法 - 进入视口时渲染
|
|
380
418
|
<Visible>
|
|
381
419
|
<HeavyComponent />
|
|
382
420
|
</Visible>
|
|
383
421
|
|
|
384
|
-
//
|
|
422
|
+
// 带占位符
|
|
385
423
|
<Visible fallback={<Skeleton />}>
|
|
386
424
|
<Image src={url} />
|
|
387
425
|
</Visible>
|
|
388
426
|
|
|
389
|
-
//
|
|
427
|
+
// 提前预加载(rootMargin)
|
|
390
428
|
<Visible rootMargin="200px" fallback={<Placeholder />}>
|
|
391
429
|
<LazyImage />
|
|
392
430
|
</Visible>
|
|
393
431
|
|
|
394
|
-
//
|
|
432
|
+
// 切换可见性(once=false 时离开视口会卸载)
|
|
395
433
|
<Visible once={false} onVisibilityChange={(v) => console.log(v)}>
|
|
396
434
|
<VideoPlayer />
|
|
397
435
|
</Visible>
|
|
398
436
|
```
|
|
399
437
|
|
|
400
|
-
##
|
|
438
|
+
## 开发
|
|
401
439
|
|
|
402
440
|
```bash
|
|
403
|
-
#
|
|
441
|
+
# 安装依赖
|
|
404
442
|
bun install
|
|
405
443
|
|
|
406
|
-
#
|
|
444
|
+
# 运行测试
|
|
407
445
|
bun test
|
|
408
446
|
|
|
409
|
-
#
|
|
447
|
+
# 代码检查
|
|
410
448
|
bun run lint
|
|
411
449
|
|
|
412
|
-
#
|
|
450
|
+
# 构建
|
|
413
451
|
bun run build
|
|
414
452
|
```
|
|
415
453
|
|
package/dist/ClientOnly.d.ts
CHANGED
package/dist/Repeat.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import type { ReactNode } from
|
|
2
|
-
import { type ForProps } from
|
|
3
|
-
export interface RepeatProps extends Omit<ForProps<number>,
|
|
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
|
|
2
|
-
import { type ForProps } from
|
|
3
|
-
export interface SplitProps extends Omit<ForProps<string>,
|
|
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);
|
|
@@ -96,18 +86,6 @@ function For({ each, children, keyExtractor, fallback = null, wrapper, reverse }
|
|
|
96
86
|
return wrapper && isValidElement(wrapper) ? cloneElement(wrapper, {}, elements) : elements;
|
|
97
87
|
}
|
|
98
88
|
|
|
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
89
|
function defaultIsEmpty(data) {
|
|
112
90
|
if (data == null) return true;
|
|
113
91
|
if (Array.isArray(data)) return data.length === 0;
|
|
@@ -206,6 +184,19 @@ function Switch({ children, fallback = null }) {
|
|
|
206
184
|
return defaultContent;
|
|
207
185
|
}
|
|
208
186
|
|
|
187
|
+
function Timeout({ ms, children, mode = "after", fallback = null, onTimeout }) {
|
|
188
|
+
const [ready, setReady] = useState(mode === "before");
|
|
189
|
+
useEffect(() => {
|
|
190
|
+
const timer = setTimeout(() => {
|
|
191
|
+
setReady((prev) => !prev);
|
|
192
|
+
onTimeout?.();
|
|
193
|
+
}, ms);
|
|
194
|
+
return () => clearTimeout(timer);
|
|
195
|
+
}, [ms, onTimeout]);
|
|
196
|
+
if (mode === "after") return ready ? children : fallback;
|
|
197
|
+
return ready ? children : null;
|
|
198
|
+
}
|
|
199
|
+
|
|
209
200
|
function Visible({ children, fallback = null, rootMargin = "0px", threshold = 0, once = true, onVisibilityChange }) {
|
|
210
201
|
const [isVisible, setIsVisible] = useState(false);
|
|
211
202
|
const [hasBeenVisible, setHasBeenVisible] = useState(false);
|
|
@@ -257,4 +248,4 @@ function Visible({ children, fallback = null, rootMargin = "0px", threshold = 0,
|
|
|
257
248
|
});
|
|
258
249
|
}
|
|
259
250
|
|
|
260
|
-
export { Await,
|
|
251
|
+
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.
|
|
3
|
+
"version": "2.5.0",
|
|
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",
|