sigpro 1.0.14 → 1.2.39
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 +164 -1008
- package/dist/sigpro.editor.js +1 -0
- package/dist/sigpro.grid.js +78 -0
- package/dist/sigpro.js +1 -0
- package/dist/sigpro.ui.css +2 -0
- package/dist/sigpro.ui.js +1 -0
- package/dist/sigpro.utils.js +1 -0
- package/dist/sigpro.vite.js +4 -0
- package/package.json +64 -14
- package/sigpro.d.ts +395 -0
- package/.github/workflows/publish.yml +0 -25
- package/bun.lock +0 -385
- package/docs/404.html +0 -22
- package/docs/api/components.html +0 -595
- package/docs/api/effects.html +0 -787
- package/docs/api/fetch.html +0 -873
- package/docs/api/pages.html +0 -405
- package/docs/api/quick.html +0 -217
- package/docs/api/routing.html +0 -628
- package/docs/api/signals.html +0 -683
- package/docs/api/storage.html +0 -820
- package/docs/assets/api_components.md.BlFwj17l.js +0 -571
- package/docs/assets/api_components.md.BlFwj17l.lean.js +0 -1
- package/docs/assets/api_effects.md.Br_yStBS.js +0 -763
- package/docs/assets/api_effects.md.Br_yStBS.lean.js +0 -1
- package/docs/assets/api_fetch.md.DQLBJSoq.js +0 -849
- package/docs/assets/api_fetch.md.DQLBJSoq.lean.js +0 -1
- package/docs/assets/api_pages.md.BP19nHXw.js +0 -381
- package/docs/assets/api_pages.md.BP19nHXw.lean.js +0 -1
- package/docs/assets/api_quick.md.BDS3ttnt.js +0 -193
- package/docs/assets/api_quick.md.BDS3ttnt.lean.js +0 -1
- package/docs/assets/api_routing.md.7SNAZXtp.js +0 -604
- package/docs/assets/api_routing.md.7SNAZXtp.lean.js +0 -1
- package/docs/assets/api_signals.md.CrW68-BA.js +0 -659
- package/docs/assets/api_signals.md.CrW68-BA.lean.js +0 -1
- package/docs/assets/api_storage.md.COEWBXHk.js +0 -796
- package/docs/assets/api_storage.md.COEWBXHk.lean.js +0 -1
- package/docs/assets/app.DtmzNmNl.js +0 -1
- package/docs/assets/chunks/framework.C8AWLET_.js +0 -19
- package/docs/assets/chunks/theme.yfWKMLQM.js +0 -1
- package/docs/assets/guide_getting-started.md.BeQpK3vd.js +0 -172
- package/docs/assets/guide_getting-started.md.BeQpK3vd.lean.js +0 -1
- package/docs/assets/guide_why.md.DXchYMN-.js +0 -23
- package/docs/assets/guide_why.md.DXchYMN-.lean.js +0 -1
- package/docs/assets/index.md.uvMJmU4o.js +0 -1
- package/docs/assets/index.md.uvMJmU4o.lean.js +0 -1
- package/docs/assets/inter-italic-cyrillic-ext.r48I6akx.woff2 +0 -0
- package/docs/assets/inter-italic-cyrillic.By2_1cv3.woff2 +0 -0
- package/docs/assets/inter-italic-greek-ext.1u6EdAuj.woff2 +0 -0
- package/docs/assets/inter-italic-greek.DJ8dCoTZ.woff2 +0 -0
- package/docs/assets/inter-italic-latin-ext.CN1xVJS-.woff2 +0 -0
- package/docs/assets/inter-italic-latin.C2AdPX0b.woff2 +0 -0
- package/docs/assets/inter-italic-vietnamese.BSbpV94h.woff2 +0 -0
- package/docs/assets/inter-roman-cyrillic-ext.BBPuwvHQ.woff2 +0 -0
- package/docs/assets/inter-roman-cyrillic.C5lxZ8CY.woff2 +0 -0
- package/docs/assets/inter-roman-greek-ext.CqjqNYQ-.woff2 +0 -0
- package/docs/assets/inter-roman-greek.BBVDIX6e.woff2 +0 -0
- package/docs/assets/inter-roman-latin-ext.4ZJIpNVo.woff2 +0 -0
- package/docs/assets/inter-roman-latin.Di8DUHzh.woff2 +0 -0
- package/docs/assets/inter-roman-vietnamese.BjW4sHH5.woff2 +0 -0
- package/docs/assets/style.DJRheFKp.css +0 -1
- package/docs/assets/ui_intro.md.gZ21GFqo.js +0 -1
- package/docs/assets/ui_intro.md.gZ21GFqo.lean.js +0 -1
- package/docs/assets/vite_plugin.md.gDWEi8f0.js +0 -225
- package/docs/assets/vite_plugin.md.gDWEi8f0.lean.js +0 -1
- package/docs/guide/getting-started.html +0 -196
- package/docs/guide/why.html +0 -47
- package/docs/hashmap.json +0 -1
- package/docs/index.html +0 -25
- package/docs/logo.svg +0 -118
- package/docs/ui/intro.html +0 -25
- package/docs/vite/plugin.html +0 -249
- package/docs/vp-icons.css +0 -1
- package/index.js +0 -3
- package/packages/docs/.vitepress/cache/deps/@theme_index.js +0 -275
- package/packages/docs/.vitepress/cache/deps/@theme_index.js.map +0 -7
- package/packages/docs/.vitepress/cache/deps/_metadata.json +0 -40
- package/packages/docs/.vitepress/cache/deps/chunk-3S55Y3P7.js +0 -12951
- package/packages/docs/.vitepress/cache/deps/chunk-3S55Y3P7.js.map +0 -7
- package/packages/docs/.vitepress/cache/deps/chunk-RLEUDPPB.js +0 -9719
- package/packages/docs/.vitepress/cache/deps/chunk-RLEUDPPB.js.map +0 -7
- package/packages/docs/.vitepress/cache/deps/package.json +0 -3
- package/packages/docs/.vitepress/cache/deps/vitepress___@vue_devtools-api.js +0 -4505
- package/packages/docs/.vitepress/cache/deps/vitepress___@vue_devtools-api.js.map +0 -7
- package/packages/docs/.vitepress/cache/deps/vitepress___@vueuse_core.js +0 -583
- package/packages/docs/.vitepress/cache/deps/vitepress___@vueuse_core.js.map +0 -7
- package/packages/docs/.vitepress/cache/deps/vue.js +0 -347
- package/packages/docs/.vitepress/cache/deps/vue.js.map +0 -7
- package/packages/docs/.vitepress/config.js +0 -68
- package/packages/docs/api/components.md +0 -760
- package/packages/docs/api/effects.md +0 -1039
- package/packages/docs/api/fetch.md +0 -998
- package/packages/docs/api/pages.md +0 -497
- package/packages/docs/api/quick.md +0 -436
- package/packages/docs/api/routing.md +0 -784
- package/packages/docs/api/signals.md +0 -899
- package/packages/docs/api/storage.md +0 -952
- package/packages/docs/guide/getting-started.md +0 -308
- package/packages/docs/guide/why.md +0 -135
- package/packages/docs/index.md +0 -84
- package/packages/docs/logo.svg +0 -118
- package/packages/docs/public/logo.svg +0 -118
- package/packages/docs/ui/intro.md +0 -16
- package/packages/docs/vite/plugin.md +0 -423
- package/packages/sigpro/plugin.js +0 -91
- package/packages/sigpro/plugin.min.js +0 -1
- package/packages/sigpro/sigpro.js +0 -631
- package/packages/sigpro/sigpro.min.js +0 -1
- package/vite.config.js +0 -24
|
@@ -1,1039 +0,0 @@
|
|
|
1
|
-
# Effects API 🔄
|
|
2
|
-
|
|
3
|
-
Effects are the bridge between reactive signals and side effects in your application. They automatically track signal dependencies and re-run whenever those signals change, enabling everything from DOM updates to data fetching and localStorage synchronization.
|
|
4
|
-
|
|
5
|
-
## Core Concepts
|
|
6
|
-
|
|
7
|
-
### What is an Effect?
|
|
8
|
-
|
|
9
|
-
An effect is a function that:
|
|
10
|
-
- **Runs immediately** when created
|
|
11
|
-
- **Tracks all signals** read during its execution
|
|
12
|
-
- **Re-runs automatically** when any tracked signal changes
|
|
13
|
-
- **Can return a cleanup function** that runs before the next execution or when the effect is stopped
|
|
14
|
-
|
|
15
|
-
### How Effects Work
|
|
16
|
-
|
|
17
|
-
1. When an effect runs, it sets itself as the `activeEffect`
|
|
18
|
-
2. Any signal read during execution adds the effect to its subscribers
|
|
19
|
-
3. When a signal changes, it queues all its subscribers
|
|
20
|
-
4. Effects are batched and run in the next microtask
|
|
21
|
-
5. If an effect returns a function, it's stored as a cleanup handler
|
|
22
|
-
|
|
23
|
-
## `$.effect(effectFn)`
|
|
24
|
-
|
|
25
|
-
Creates a reactive effect that automatically tracks dependencies and re-runs when they change.
|
|
26
|
-
|
|
27
|
-
```javascript
|
|
28
|
-
import { $ } from 'sigpro';
|
|
29
|
-
|
|
30
|
-
const count = $(0);
|
|
31
|
-
|
|
32
|
-
$.effect(() => {
|
|
33
|
-
console.log(`Count is: ${count()}`);
|
|
34
|
-
});
|
|
35
|
-
// Logs: "Count is: 0"
|
|
36
|
-
|
|
37
|
-
count(1);
|
|
38
|
-
// Logs: "Count is: 1"
|
|
39
|
-
```
|
|
40
|
-
|
|
41
|
-
## 📋 API Reference
|
|
42
|
-
|
|
43
|
-
| Pattern | Example | Description |
|
|
44
|
-
|---------|---------|-------------|
|
|
45
|
-
| Basic Effect | `$.effect(() => console.log(count()))` | Run on dependency changes |
|
|
46
|
-
| With Cleanup | `$.effect(() => { timer = setInterval(...); return () => clearInterval(timer) })` | Return cleanup function |
|
|
47
|
-
| Stop Effect | `const stop = $.effect(...); stop()` | Manually stop an effect |
|
|
48
|
-
|
|
49
|
-
### Effect Object (Internal)
|
|
50
|
-
|
|
51
|
-
| Property/Method | Description |
|
|
52
|
-
|-----------------|-------------|
|
|
53
|
-
| `dependencies` | Set of signal subscriber sets this effect belongs to |
|
|
54
|
-
| `cleanupHandlers` | Set of cleanup functions to run before next execution |
|
|
55
|
-
| `run()` | Executes the effect and tracks dependencies |
|
|
56
|
-
| `stop()` | Stops the effect and runs all cleanup handlers |
|
|
57
|
-
|
|
58
|
-
## 🎯 Basic Examples
|
|
59
|
-
|
|
60
|
-
### Console Logging
|
|
61
|
-
|
|
62
|
-
```javascript
|
|
63
|
-
import { $ } from 'sigpro';
|
|
64
|
-
|
|
65
|
-
const name = $('World');
|
|
66
|
-
const count = $(0);
|
|
67
|
-
|
|
68
|
-
$.effect(() => {
|
|
69
|
-
console.log(`Hello ${name()}! Count is ${count()}`);
|
|
70
|
-
});
|
|
71
|
-
// Logs: "Hello World! Count is 0"
|
|
72
|
-
|
|
73
|
-
name('John');
|
|
74
|
-
// Logs: "Hello John! Count is 0"
|
|
75
|
-
|
|
76
|
-
count(5);
|
|
77
|
-
// Logs: "Hello John! Count is 5"
|
|
78
|
-
```
|
|
79
|
-
|
|
80
|
-
### DOM Updates
|
|
81
|
-
|
|
82
|
-
```javascript
|
|
83
|
-
import { $ } from 'sigpro';
|
|
84
|
-
|
|
85
|
-
const count = $(0);
|
|
86
|
-
const element = document.getElementById('counter');
|
|
87
|
-
|
|
88
|
-
$.effect(() => {
|
|
89
|
-
element.textContent = `Count: ${count()}`;
|
|
90
|
-
});
|
|
91
|
-
|
|
92
|
-
// Updates DOM automatically when count changes
|
|
93
|
-
count(10); // Element text becomes "Count: 10"
|
|
94
|
-
```
|
|
95
|
-
|
|
96
|
-
### Document Title
|
|
97
|
-
|
|
98
|
-
```javascript
|
|
99
|
-
import { $ } from 'sigpro';
|
|
100
|
-
|
|
101
|
-
const page = $('home');
|
|
102
|
-
const unreadCount = $(0);
|
|
103
|
-
|
|
104
|
-
$.effect(() => {
|
|
105
|
-
const base = page() === 'home' ? 'Home' : 'Dashboard';
|
|
106
|
-
const unread = unreadCount() > 0 ? ` (${unreadCount()})` : '';
|
|
107
|
-
document.title = `${base}${unread} - My App`;
|
|
108
|
-
});
|
|
109
|
-
|
|
110
|
-
page('dashboard'); // Title: "Dashboard - My App"
|
|
111
|
-
unreadCount(3); // Title: "Dashboard (3) - My App"
|
|
112
|
-
```
|
|
113
|
-
|
|
114
|
-
## 🧹 Effects with Cleanup
|
|
115
|
-
|
|
116
|
-
Cleanup functions are essential for managing resources like intervals, event listeners, and subscriptions.
|
|
117
|
-
|
|
118
|
-
### Basic Cleanup
|
|
119
|
-
|
|
120
|
-
```javascript
|
|
121
|
-
import { $ } from 'sigpro';
|
|
122
|
-
|
|
123
|
-
const userId = $(1);
|
|
124
|
-
|
|
125
|
-
$.effect(() => {
|
|
126
|
-
const id = userId();
|
|
127
|
-
console.log(`Setting up timer for user ${id}`);
|
|
128
|
-
|
|
129
|
-
const timer = setInterval(() => {
|
|
130
|
-
console.log(`Polling user ${id}...`);
|
|
131
|
-
}, 1000);
|
|
132
|
-
|
|
133
|
-
// Cleanup runs before next effect execution
|
|
134
|
-
return () => {
|
|
135
|
-
console.log(`Cleaning up timer for user ${id}`);
|
|
136
|
-
clearInterval(timer);
|
|
137
|
-
};
|
|
138
|
-
});
|
|
139
|
-
// Sets up timer for user 1
|
|
140
|
-
|
|
141
|
-
userId(2);
|
|
142
|
-
// Cleans up timer for user 1
|
|
143
|
-
// Sets up timer for user 2
|
|
144
|
-
```
|
|
145
|
-
|
|
146
|
-
### Event Listener Cleanup
|
|
147
|
-
|
|
148
|
-
```javascript
|
|
149
|
-
import { $ } from 'sigpro';
|
|
150
|
-
|
|
151
|
-
const isListening = $(false);
|
|
152
|
-
|
|
153
|
-
$.effect(() => {
|
|
154
|
-
if (!isListening()) return;
|
|
155
|
-
|
|
156
|
-
const handleClick = (e) => {
|
|
157
|
-
console.log('Window clicked:', e.clientX, e.clientY);
|
|
158
|
-
};
|
|
159
|
-
|
|
160
|
-
window.addEventListener('click', handleClick);
|
|
161
|
-
console.log('Click listener added');
|
|
162
|
-
|
|
163
|
-
return () => {
|
|
164
|
-
window.removeEventListener('click', handleClick);
|
|
165
|
-
console.log('Click listener removed');
|
|
166
|
-
};
|
|
167
|
-
});
|
|
168
|
-
|
|
169
|
-
isListening(true); // Adds listener
|
|
170
|
-
isListening(false); // Removes listener
|
|
171
|
-
isListening(true); // Adds listener again
|
|
172
|
-
```
|
|
173
|
-
|
|
174
|
-
### WebSocket Connection
|
|
175
|
-
|
|
176
|
-
```javascript
|
|
177
|
-
import { $ } from 'sigpro';
|
|
178
|
-
|
|
179
|
-
const room = $('general');
|
|
180
|
-
const messages = $([]);
|
|
181
|
-
|
|
182
|
-
$.effect(() => {
|
|
183
|
-
const currentRoom = room();
|
|
184
|
-
console.log(`Connecting to room: ${currentRoom}`);
|
|
185
|
-
|
|
186
|
-
const ws = new WebSocket(`wss://chat.example.com/${currentRoom}`);
|
|
187
|
-
|
|
188
|
-
ws.onmessage = (event) => {
|
|
189
|
-
messages([...messages(), JSON.parse(event.data)]);
|
|
190
|
-
};
|
|
191
|
-
|
|
192
|
-
ws.onerror = (error) => {
|
|
193
|
-
console.error('WebSocket error:', error);
|
|
194
|
-
};
|
|
195
|
-
|
|
196
|
-
// Cleanup: close connection when room changes
|
|
197
|
-
return () => {
|
|
198
|
-
console.log(`Disconnecting from room: ${currentRoom}`);
|
|
199
|
-
ws.close();
|
|
200
|
-
};
|
|
201
|
-
});
|
|
202
|
-
|
|
203
|
-
room('random'); // Closes 'general' connection, opens 'random'
|
|
204
|
-
```
|
|
205
|
-
|
|
206
|
-
## ⏱️ Effect Timing and Batching
|
|
207
|
-
|
|
208
|
-
### Microtask Batching
|
|
209
|
-
|
|
210
|
-
Effects are batched using `queueMicrotask` for optimal performance:
|
|
211
|
-
|
|
212
|
-
```javascript
|
|
213
|
-
import { $ } from 'sigpro';
|
|
214
|
-
|
|
215
|
-
const a = $(1);
|
|
216
|
-
const b = $(2);
|
|
217
|
-
const c = $(3);
|
|
218
|
-
|
|
219
|
-
$.effect(() => {
|
|
220
|
-
console.log('Effect ran with:', a(), b(), c());
|
|
221
|
-
});
|
|
222
|
-
// Logs immediately: "Effect ran with: 1 2 3"
|
|
223
|
-
|
|
224
|
-
// Multiple updates in same tick - only one effect run!
|
|
225
|
-
a(10);
|
|
226
|
-
b(20);
|
|
227
|
-
c(30);
|
|
228
|
-
// Only logs once: "Effect ran with: 10 20 30"
|
|
229
|
-
```
|
|
230
|
-
|
|
231
|
-
### Async Effects
|
|
232
|
-
|
|
233
|
-
Effects can be asynchronous, but be careful with dependency tracking:
|
|
234
|
-
|
|
235
|
-
```javascript
|
|
236
|
-
import { $ } from 'sigpro';
|
|
237
|
-
|
|
238
|
-
const userId = $(1);
|
|
239
|
-
const userData = $(null);
|
|
240
|
-
|
|
241
|
-
$.effect(() => {
|
|
242
|
-
const id = userId();
|
|
243
|
-
console.log(`Fetching user ${id}...`);
|
|
244
|
-
|
|
245
|
-
// Only id() is tracked (synchronous part)
|
|
246
|
-
fetch(`/api/users/${id}`)
|
|
247
|
-
.then(res => res.json())
|
|
248
|
-
.then(data => {
|
|
249
|
-
// This runs later - no dependency tracking here!
|
|
250
|
-
userData(data);
|
|
251
|
-
});
|
|
252
|
-
});
|
|
253
|
-
|
|
254
|
-
userId(2); // Triggers effect again, cancels previous fetch
|
|
255
|
-
```
|
|
256
|
-
|
|
257
|
-
### Effect with AbortController
|
|
258
|
-
|
|
259
|
-
For proper async cleanup with fetch:
|
|
260
|
-
|
|
261
|
-
```javascript
|
|
262
|
-
import { $ } from 'sigpro';
|
|
263
|
-
|
|
264
|
-
const userId = $(1);
|
|
265
|
-
const userData = $(null);
|
|
266
|
-
const loading = $(false);
|
|
267
|
-
|
|
268
|
-
$.effect(() => {
|
|
269
|
-
const id = userId();
|
|
270
|
-
const controller = new AbortController();
|
|
271
|
-
|
|
272
|
-
loading(true);
|
|
273
|
-
|
|
274
|
-
fetch(`/api/users/${id}`, { signal: controller.signal })
|
|
275
|
-
.then(res => res.json())
|
|
276
|
-
.then(data => {
|
|
277
|
-
userData(data);
|
|
278
|
-
loading(false);
|
|
279
|
-
})
|
|
280
|
-
.catch(err => {
|
|
281
|
-
if (err.name !== 'AbortError') {
|
|
282
|
-
console.error('Fetch error:', err);
|
|
283
|
-
loading(false);
|
|
284
|
-
}
|
|
285
|
-
});
|
|
286
|
-
|
|
287
|
-
// Cleanup: abort fetch if userId changes before completion
|
|
288
|
-
return () => {
|
|
289
|
-
controller.abort();
|
|
290
|
-
};
|
|
291
|
-
});
|
|
292
|
-
```
|
|
293
|
-
|
|
294
|
-
## 🎨 Advanced Effect Patterns
|
|
295
|
-
|
|
296
|
-
### Debounced Effects
|
|
297
|
-
|
|
298
|
-
```javascript
|
|
299
|
-
import { $ } from 'sigpro';
|
|
300
|
-
|
|
301
|
-
const searchTerm = $('');
|
|
302
|
-
const results = $([]);
|
|
303
|
-
let debounceTimeout;
|
|
304
|
-
|
|
305
|
-
$.effect(() => {
|
|
306
|
-
const term = searchTerm();
|
|
307
|
-
|
|
308
|
-
// Clear previous timeout
|
|
309
|
-
clearTimeout(debounceTimeout);
|
|
310
|
-
|
|
311
|
-
// Don't search if term is too short
|
|
312
|
-
if (term.length < 3) {
|
|
313
|
-
results([]);
|
|
314
|
-
return;
|
|
315
|
-
}
|
|
316
|
-
|
|
317
|
-
// Debounce search
|
|
318
|
-
debounceTimeout = setTimeout(async () => {
|
|
319
|
-
console.log('Searching for:', term);
|
|
320
|
-
const data = await fetch(`/api/search?q=${term}`).then(r => r.json());
|
|
321
|
-
results(data);
|
|
322
|
-
}, 300);
|
|
323
|
-
|
|
324
|
-
// Cleanup on effect re-run
|
|
325
|
-
return () => clearTimeout(debounceTimeout);
|
|
326
|
-
});
|
|
327
|
-
```
|
|
328
|
-
|
|
329
|
-
### Throttled Effects
|
|
330
|
-
|
|
331
|
-
```javascript
|
|
332
|
-
import { $ } from 'sigpro';
|
|
333
|
-
|
|
334
|
-
const scrollPosition = $(0);
|
|
335
|
-
let lastRun = 0;
|
|
336
|
-
let rafId = null;
|
|
337
|
-
|
|
338
|
-
$.effect(() => {
|
|
339
|
-
const pos = scrollPosition();
|
|
340
|
-
|
|
341
|
-
// Throttle with requestAnimationFrame
|
|
342
|
-
if (rafId) cancelAnimationFrame(rafId);
|
|
343
|
-
|
|
344
|
-
rafId = requestAnimationFrame(() => {
|
|
345
|
-
console.log('Scroll position:', pos);
|
|
346
|
-
updateScrollUI(pos);
|
|
347
|
-
lastRun = Date.now();
|
|
348
|
-
rafId = null;
|
|
349
|
-
});
|
|
350
|
-
|
|
351
|
-
return () => {
|
|
352
|
-
if (rafId) {
|
|
353
|
-
cancelAnimationFrame(rafId);
|
|
354
|
-
rafId = null;
|
|
355
|
-
}
|
|
356
|
-
};
|
|
357
|
-
});
|
|
358
|
-
|
|
359
|
-
// Even with many updates, effect runs at most once per frame
|
|
360
|
-
for (let i = 0; i < 100; i++) {
|
|
361
|
-
scrollPosition(i);
|
|
362
|
-
}
|
|
363
|
-
```
|
|
364
|
-
|
|
365
|
-
### Conditional Effects
|
|
366
|
-
|
|
367
|
-
```javascript
|
|
368
|
-
import { $ } from 'sigpro';
|
|
369
|
-
|
|
370
|
-
const isEnabled = $(false);
|
|
371
|
-
const value = $(0);
|
|
372
|
-
const threshold = $(10);
|
|
373
|
-
|
|
374
|
-
$.effect(() => {
|
|
375
|
-
// Effect only runs when isEnabled is true
|
|
376
|
-
if (!isEnabled()) return;
|
|
377
|
-
|
|
378
|
-
console.log(`Monitoring value: ${value()}, threshold: ${threshold()}`);
|
|
379
|
-
|
|
380
|
-
if (value() > threshold()) {
|
|
381
|
-
alert(`Value ${value()} exceeded threshold ${threshold()}!`);
|
|
382
|
-
}
|
|
383
|
-
});
|
|
384
|
-
|
|
385
|
-
isEnabled(true); // Effect starts monitoring
|
|
386
|
-
value(15); // Triggers alert
|
|
387
|
-
isEnabled(false); // Effect stops (still runs, but condition prevents logic)
|
|
388
|
-
```
|
|
389
|
-
|
|
390
|
-
### Effect with Multiple Cleanups
|
|
391
|
-
|
|
392
|
-
```javascript
|
|
393
|
-
import { $ } from 'sigpro';
|
|
394
|
-
|
|
395
|
-
const config = $({ theme: 'light', notifications: true });
|
|
396
|
-
|
|
397
|
-
$.effect(() => {
|
|
398
|
-
const { theme, notifications } = config();
|
|
399
|
-
const cleanups = [];
|
|
400
|
-
|
|
401
|
-
// Setup theme
|
|
402
|
-
document.body.className = `theme-${theme}`;
|
|
403
|
-
cleanups.push(() => {
|
|
404
|
-
document.body.classList.remove(`theme-${theme}`);
|
|
405
|
-
});
|
|
406
|
-
|
|
407
|
-
// Setup notifications
|
|
408
|
-
if (notifications) {
|
|
409
|
-
const handler = (e) => console.log('Notification:', e.detail);
|
|
410
|
-
window.addEventListener('notification', handler);
|
|
411
|
-
cleanups.push(() => {
|
|
412
|
-
window.removeEventListener('notification', handler);
|
|
413
|
-
});
|
|
414
|
-
}
|
|
415
|
-
|
|
416
|
-
// Return combined cleanup
|
|
417
|
-
return () => {
|
|
418
|
-
cleanups.forEach(cleanup => cleanup());
|
|
419
|
-
};
|
|
420
|
-
});
|
|
421
|
-
```
|
|
422
|
-
|
|
423
|
-
## 🎯 Effects in Components
|
|
424
|
-
|
|
425
|
-
### Component Lifecycle
|
|
426
|
-
|
|
427
|
-
```javascript
|
|
428
|
-
import { $, html } from 'sigpro';
|
|
429
|
-
|
|
430
|
-
$.component('timer-display', () => {
|
|
431
|
-
const seconds = $(0);
|
|
432
|
-
|
|
433
|
-
// Effect for timer - automatically cleaned up when component unmounts
|
|
434
|
-
$.effect(() => {
|
|
435
|
-
const interval = setInterval(() => {
|
|
436
|
-
seconds(s => s + 1);
|
|
437
|
-
}, 1000);
|
|
438
|
-
|
|
439
|
-
return () => clearInterval(interval);
|
|
440
|
-
});
|
|
441
|
-
|
|
442
|
-
return html`
|
|
443
|
-
<div>
|
|
444
|
-
<h2>Timer: ${seconds}s</h2>
|
|
445
|
-
</div>
|
|
446
|
-
`;
|
|
447
|
-
});
|
|
448
|
-
```
|
|
449
|
-
|
|
450
|
-
### Effects with Props
|
|
451
|
-
|
|
452
|
-
```javascript
|
|
453
|
-
import { $, html } from 'sigpro';
|
|
454
|
-
|
|
455
|
-
$.component('data-viewer', (props) => {
|
|
456
|
-
const data = $(null);
|
|
457
|
-
const error = $(null);
|
|
458
|
-
|
|
459
|
-
// Effect reacts to prop changes
|
|
460
|
-
$.effect(() => {
|
|
461
|
-
const url = props.url();
|
|
462
|
-
if (!url) return;
|
|
463
|
-
|
|
464
|
-
const controller = new AbortController();
|
|
465
|
-
|
|
466
|
-
fetch(url, { signal: controller.signal })
|
|
467
|
-
.then(res => res.json())
|
|
468
|
-
.then(data)
|
|
469
|
-
.catch(err => {
|
|
470
|
-
if (err.name !== 'AbortError') {
|
|
471
|
-
error(err.message);
|
|
472
|
-
}
|
|
473
|
-
});
|
|
474
|
-
|
|
475
|
-
return () => controller.abort();
|
|
476
|
-
});
|
|
477
|
-
|
|
478
|
-
return html`
|
|
479
|
-
<div>
|
|
480
|
-
${() => {
|
|
481
|
-
if (error()) return html`<div class="error">${error()}</div>`;
|
|
482
|
-
if (!data()) return html`<div>Loading...</div>`;
|
|
483
|
-
return html`<pre>${JSON.stringify(data(), null, 2)}</pre>`;
|
|
484
|
-
}}
|
|
485
|
-
</div>
|
|
486
|
-
`;
|
|
487
|
-
}, ['url']);
|
|
488
|
-
```
|
|
489
|
-
|
|
490
|
-
## 🔧 Effect Management
|
|
491
|
-
|
|
492
|
-
### Stopping Effects
|
|
493
|
-
|
|
494
|
-
```javascript
|
|
495
|
-
import { $ } from 'sigpro';
|
|
496
|
-
|
|
497
|
-
const count = $(0);
|
|
498
|
-
|
|
499
|
-
// Start effect
|
|
500
|
-
const stopEffect = $.effect(() => {
|
|
501
|
-
console.log('Count:', count());
|
|
502
|
-
});
|
|
503
|
-
|
|
504
|
-
count(1); // Logs: "Count: 1"
|
|
505
|
-
count(2); // Logs: "Count: 2"
|
|
506
|
-
|
|
507
|
-
// Stop the effect
|
|
508
|
-
stopEffect();
|
|
509
|
-
|
|
510
|
-
count(3); // No logging - effect is stopped
|
|
511
|
-
```
|
|
512
|
-
|
|
513
|
-
### Conditional Effect Stopping
|
|
514
|
-
|
|
515
|
-
```javascript
|
|
516
|
-
import { $ } from 'sigpro';
|
|
517
|
-
|
|
518
|
-
const isActive = $(true);
|
|
519
|
-
const count = $(0);
|
|
520
|
-
|
|
521
|
-
let currentEffect = null;
|
|
522
|
-
|
|
523
|
-
$.effect(() => {
|
|
524
|
-
if (isActive()) {
|
|
525
|
-
// Start or restart the monitoring effect
|
|
526
|
-
if (currentEffect) currentEffect();
|
|
527
|
-
|
|
528
|
-
currentEffect = $.effect(() => {
|
|
529
|
-
console.log('Monitoring count:', count());
|
|
530
|
-
});
|
|
531
|
-
} else {
|
|
532
|
-
// Stop monitoring
|
|
533
|
-
if (currentEffect) {
|
|
534
|
-
currentEffect();
|
|
535
|
-
currentEffect = null;
|
|
536
|
-
}
|
|
537
|
-
}
|
|
538
|
-
});
|
|
539
|
-
```
|
|
540
|
-
|
|
541
|
-
### Nested Effects
|
|
542
|
-
|
|
543
|
-
```javascript
|
|
544
|
-
import { $ } from 'sigpro';
|
|
545
|
-
|
|
546
|
-
const user = $({ id: 1, name: 'John' });
|
|
547
|
-
const settings = $({ theme: 'dark' });
|
|
548
|
-
|
|
549
|
-
$.effect(() => {
|
|
550
|
-
console.log('User changed:', user().name);
|
|
551
|
-
|
|
552
|
-
// Nested effect - tracks settings independently
|
|
553
|
-
$.effect(() => {
|
|
554
|
-
console.log('Settings changed:', settings().theme);
|
|
555
|
-
});
|
|
556
|
-
|
|
557
|
-
// When user changes, the nested effect is recreated
|
|
558
|
-
});
|
|
559
|
-
```
|
|
560
|
-
|
|
561
|
-
## 🚀 Real-World Examples
|
|
562
|
-
|
|
563
|
-
### Auto-saving Form
|
|
564
|
-
|
|
565
|
-
```javascript
|
|
566
|
-
import { $ } from 'sigpro';
|
|
567
|
-
|
|
568
|
-
const formData = $({
|
|
569
|
-
title: '',
|
|
570
|
-
content: '',
|
|
571
|
-
tags: []
|
|
572
|
-
});
|
|
573
|
-
|
|
574
|
-
const lastSaved = $(null);
|
|
575
|
-
const saveStatus = $('idle'); // 'idle', 'saving', 'saved', 'error'
|
|
576
|
-
let saveTimeout;
|
|
577
|
-
|
|
578
|
-
$.effect(() => {
|
|
579
|
-
const data = formData();
|
|
580
|
-
|
|
581
|
-
// Clear previous timeout
|
|
582
|
-
clearTimeout(saveTimeout);
|
|
583
|
-
|
|
584
|
-
// Don't save empty form
|
|
585
|
-
if (!data.title && !data.content) {
|
|
586
|
-
saveStatus('idle');
|
|
587
|
-
return;
|
|
588
|
-
}
|
|
589
|
-
|
|
590
|
-
saveStatus('saving');
|
|
591
|
-
|
|
592
|
-
// Debounce save
|
|
593
|
-
saveTimeout = setTimeout(async () => {
|
|
594
|
-
try {
|
|
595
|
-
await fetch('/api/posts', {
|
|
596
|
-
method: 'POST',
|
|
597
|
-
headers: { 'Content-Type': 'application/json' },
|
|
598
|
-
body: JSON.stringify(data)
|
|
599
|
-
});
|
|
600
|
-
saveStatus('saved');
|
|
601
|
-
lastSaved(new Date());
|
|
602
|
-
} catch (error) {
|
|
603
|
-
saveStatus('error');
|
|
604
|
-
console.error('Auto-save failed:', error);
|
|
605
|
-
}
|
|
606
|
-
}, 1000);
|
|
607
|
-
|
|
608
|
-
return () => clearTimeout(saveTimeout);
|
|
609
|
-
});
|
|
610
|
-
|
|
611
|
-
// UI feedback
|
|
612
|
-
const statusMessage = $(() => {
|
|
613
|
-
const status = saveStatus();
|
|
614
|
-
const saved = lastSaved();
|
|
615
|
-
|
|
616
|
-
if (status === 'saving') return 'Saving...';
|
|
617
|
-
if (status === 'error') return 'Save failed';
|
|
618
|
-
if (status === 'saved' && saved) {
|
|
619
|
-
return `Last saved: ${saved().toLocaleTimeString()}`;
|
|
620
|
-
}
|
|
621
|
-
return '';
|
|
622
|
-
});
|
|
623
|
-
```
|
|
624
|
-
|
|
625
|
-
### Real-time Search with Debounce
|
|
626
|
-
|
|
627
|
-
```javascript
|
|
628
|
-
import { $ } from 'sigpro';
|
|
629
|
-
|
|
630
|
-
const searchInput = $('');
|
|
631
|
-
const searchResults = $([]);
|
|
632
|
-
const searchStatus = $('idle'); // 'idle', 'searching', 'results', 'no-results', 'error'
|
|
633
|
-
let searchTimeout;
|
|
634
|
-
let abortController = null;
|
|
635
|
-
|
|
636
|
-
$.effect(() => {
|
|
637
|
-
const query = searchInput().trim();
|
|
638
|
-
|
|
639
|
-
// Clear previous timeout
|
|
640
|
-
clearTimeout(searchTimeout);
|
|
641
|
-
|
|
642
|
-
// Cancel previous request
|
|
643
|
-
if (abortController) {
|
|
644
|
-
abortController.abort();
|
|
645
|
-
abortController = null;
|
|
646
|
-
}
|
|
647
|
-
|
|
648
|
-
// Don't search for short queries
|
|
649
|
-
if (query.length < 2) {
|
|
650
|
-
searchResults([]);
|
|
651
|
-
searchStatus('idle');
|
|
652
|
-
return;
|
|
653
|
-
}
|
|
654
|
-
|
|
655
|
-
searchStatus('searching');
|
|
656
|
-
|
|
657
|
-
// Debounce search
|
|
658
|
-
searchTimeout = setTimeout(async () => {
|
|
659
|
-
abortController = new AbortController();
|
|
660
|
-
|
|
661
|
-
try {
|
|
662
|
-
const response = await fetch(`/api/search?q=${encodeURIComponent(query)}`, {
|
|
663
|
-
signal: abortController.signal
|
|
664
|
-
});
|
|
665
|
-
|
|
666
|
-
const data = await response.json();
|
|
667
|
-
|
|
668
|
-
if (!abortController.signal.aborted) {
|
|
669
|
-
searchResults(data);
|
|
670
|
-
searchStatus(data.length ? 'results' : 'no-results');
|
|
671
|
-
abortController = null;
|
|
672
|
-
}
|
|
673
|
-
} catch (error) {
|
|
674
|
-
if (error.name !== 'AbortError') {
|
|
675
|
-
console.error('Search failed:', error);
|
|
676
|
-
searchStatus('error');
|
|
677
|
-
}
|
|
678
|
-
}
|
|
679
|
-
}, 300);
|
|
680
|
-
|
|
681
|
-
return () => {
|
|
682
|
-
clearTimeout(searchTimeout);
|
|
683
|
-
if (abortController) {
|
|
684
|
-
abortController.abort();
|
|
685
|
-
abortController = null;
|
|
686
|
-
}
|
|
687
|
-
};
|
|
688
|
-
});
|
|
689
|
-
```
|
|
690
|
-
|
|
691
|
-
### Analytics Tracking
|
|
692
|
-
|
|
693
|
-
```javascript
|
|
694
|
-
import { $ } from 'sigpro';
|
|
695
|
-
|
|
696
|
-
// Analytics configuration
|
|
697
|
-
const analyticsEnabled = $(true);
|
|
698
|
-
const currentPage = $('/');
|
|
699
|
-
const userProperties = $({});
|
|
700
|
-
|
|
701
|
-
// Track page views
|
|
702
|
-
$.effect(() => {
|
|
703
|
-
if (!analyticsEnabled()) return;
|
|
704
|
-
|
|
705
|
-
const page = currentPage();
|
|
706
|
-
const properties = userProperties();
|
|
707
|
-
|
|
708
|
-
console.log('Track page view:', page, properties);
|
|
709
|
-
|
|
710
|
-
// Send to analytics
|
|
711
|
-
gtag('config', 'GA-MEASUREMENT-ID', {
|
|
712
|
-
page_path: page,
|
|
713
|
-
...properties
|
|
714
|
-
});
|
|
715
|
-
});
|
|
716
|
-
|
|
717
|
-
// Track user interactions
|
|
718
|
-
const trackEvent = (eventName, properties = {}) => {
|
|
719
|
-
$.effect(() => {
|
|
720
|
-
if (!analyticsEnabled()) return;
|
|
721
|
-
|
|
722
|
-
console.log('Track event:', eventName, properties);
|
|
723
|
-
gtag('event', eventName, properties);
|
|
724
|
-
});
|
|
725
|
-
};
|
|
726
|
-
|
|
727
|
-
// Usage
|
|
728
|
-
currentPage('/dashboard');
|
|
729
|
-
userProperties({ userId: 123, plan: 'premium' });
|
|
730
|
-
trackEvent('button_click', { buttonId: 'signup' });
|
|
731
|
-
```
|
|
732
|
-
|
|
733
|
-
### Keyboard Shortcuts
|
|
734
|
-
|
|
735
|
-
```javascript
|
|
736
|
-
import { $ } from 'sigpro';
|
|
737
|
-
|
|
738
|
-
const shortcuts = $({
|
|
739
|
-
'ctrl+s': { handler: null, description: 'Save' },
|
|
740
|
-
'ctrl+z': { handler: null, description: 'Undo' },
|
|
741
|
-
'ctrl+shift+z': { handler: null, description: 'Redo' },
|
|
742
|
-
'escape': { handler: null, description: 'Close modal' }
|
|
743
|
-
});
|
|
744
|
-
|
|
745
|
-
const pressedKeys = new Set();
|
|
746
|
-
|
|
747
|
-
$.effect(() => {
|
|
748
|
-
const handleKeyDown = (e) => {
|
|
749
|
-
const key = e.key.toLowerCase();
|
|
750
|
-
const ctrl = e.ctrlKey ? 'ctrl+' : '';
|
|
751
|
-
const shift = e.shiftKey ? 'shift+' : '';
|
|
752
|
-
const alt = e.altKey ? 'alt+' : '';
|
|
753
|
-
const meta = e.metaKey ? 'meta+' : '';
|
|
754
|
-
|
|
755
|
-
const combo = `${ctrl}${shift}${alt}${meta}${key}`.replace(/\+$/, '');
|
|
756
|
-
|
|
757
|
-
const shortcut = shortcuts()[combo];
|
|
758
|
-
if (shortcut?.handler) {
|
|
759
|
-
e.preventDefault();
|
|
760
|
-
shortcut.handler();
|
|
761
|
-
}
|
|
762
|
-
};
|
|
763
|
-
|
|
764
|
-
window.addEventListener('keydown', handleKeyDown);
|
|
765
|
-
|
|
766
|
-
return () => window.removeEventListener('keydown', handleKeyDown);
|
|
767
|
-
});
|
|
768
|
-
|
|
769
|
-
// Register shortcuts
|
|
770
|
-
shortcuts({
|
|
771
|
-
...shortcuts(),
|
|
772
|
-
'ctrl+s': {
|
|
773
|
-
handler: () => saveDocument(),
|
|
774
|
-
description: 'Save document'
|
|
775
|
-
},
|
|
776
|
-
'ctrl+z': {
|
|
777
|
-
handler: () => undo(),
|
|
778
|
-
description: 'Undo'
|
|
779
|
-
}
|
|
780
|
-
});
|
|
781
|
-
```
|
|
782
|
-
|
|
783
|
-
### Infinite Scroll
|
|
784
|
-
|
|
785
|
-
```javascript
|
|
786
|
-
import { $ } from 'sigpro';
|
|
787
|
-
|
|
788
|
-
const posts = $([]);
|
|
789
|
-
const page = $(1);
|
|
790
|
-
const hasMore = $(true);
|
|
791
|
-
const loading = $(false);
|
|
792
|
-
let observer = null;
|
|
793
|
-
|
|
794
|
-
// Load more posts
|
|
795
|
-
const loadMore = async () => {
|
|
796
|
-
if (loading() || !hasMore()) return;
|
|
797
|
-
|
|
798
|
-
loading(true);
|
|
799
|
-
try {
|
|
800
|
-
const response = await fetch(`/api/posts?page=${page()}`);
|
|
801
|
-
const newPosts = await response.json();
|
|
802
|
-
|
|
803
|
-
if (newPosts.length === 0) {
|
|
804
|
-
hasMore(false);
|
|
805
|
-
} else {
|
|
806
|
-
posts([...posts(), ...newPosts]);
|
|
807
|
-
page(p => p + 1);
|
|
808
|
-
}
|
|
809
|
-
} finally {
|
|
810
|
-
loading(false);
|
|
811
|
-
}
|
|
812
|
-
};
|
|
813
|
-
|
|
814
|
-
// Setup intersection observer for infinite scroll
|
|
815
|
-
$.effect(() => {
|
|
816
|
-
const sentinel = document.getElementById('sentinel');
|
|
817
|
-
if (!sentinel) return;
|
|
818
|
-
|
|
819
|
-
observer = new IntersectionObserver(
|
|
820
|
-
(entries) => {
|
|
821
|
-
if (entries[0].isIntersecting && !loading() && hasMore()) {
|
|
822
|
-
loadMore();
|
|
823
|
-
}
|
|
824
|
-
},
|
|
825
|
-
{ threshold: 0.1 }
|
|
826
|
-
);
|
|
827
|
-
|
|
828
|
-
observer.observe(sentinel);
|
|
829
|
-
|
|
830
|
-
return () => {
|
|
831
|
-
if (observer) {
|
|
832
|
-
observer.disconnect();
|
|
833
|
-
observer = null;
|
|
834
|
-
}
|
|
835
|
-
};
|
|
836
|
-
});
|
|
837
|
-
|
|
838
|
-
// Initial load
|
|
839
|
-
loadMore();
|
|
840
|
-
```
|
|
841
|
-
|
|
842
|
-
## 📊 Performance Considerations
|
|
843
|
-
|
|
844
|
-
| Pattern | Performance Impact | Best Practice |
|
|
845
|
-
|---------|-------------------|---------------|
|
|
846
|
-
| Multiple signal reads | O(n) per effect | Group related signals |
|
|
847
|
-
| Deep object access | Minimal | Use computed signals |
|
|
848
|
-
| Large arrays | O(n) for iteration | Memoize with computed |
|
|
849
|
-
| Frequent updates | Batched | Let batching work |
|
|
850
|
-
| Heavy computations | Blocking | Use Web Workers |
|
|
851
|
-
|
|
852
|
-
## 🎯 Best Practices
|
|
853
|
-
|
|
854
|
-
### 1. Keep Effects Focused
|
|
855
|
-
|
|
856
|
-
```javascript
|
|
857
|
-
// ❌ Avoid doing too much in one effect
|
|
858
|
-
$.effect(() => {
|
|
859
|
-
updateUI(count()); // UI update
|
|
860
|
-
saveToStorage(count()); // Storage
|
|
861
|
-
sendAnalytics(count()); // Analytics
|
|
862
|
-
validate(count()); // Validation
|
|
863
|
-
});
|
|
864
|
-
|
|
865
|
-
// ✅ Split into focused effects
|
|
866
|
-
$.effect(() => updateUI(count()));
|
|
867
|
-
$.effect(() => saveToStorage(count()));
|
|
868
|
-
$.effect(() => sendAnalytics(count()));
|
|
869
|
-
$.effect(() => validate(count()));
|
|
870
|
-
```
|
|
871
|
-
|
|
872
|
-
### 2. Always Clean Up
|
|
873
|
-
|
|
874
|
-
```javascript
|
|
875
|
-
// ❌ Missing cleanup
|
|
876
|
-
$.effect(() => {
|
|
877
|
-
const timer = setInterval(() => {}, 1000);
|
|
878
|
-
// Memory leak!
|
|
879
|
-
});
|
|
880
|
-
|
|
881
|
-
// ✅ Proper cleanup
|
|
882
|
-
$.effect(() => {
|
|
883
|
-
const timer = setInterval(() => {}, 1000);
|
|
884
|
-
return () => clearInterval(timer);
|
|
885
|
-
});
|
|
886
|
-
```
|
|
887
|
-
|
|
888
|
-
### 3. Avoid Writing to Signals in Effects
|
|
889
|
-
|
|
890
|
-
```javascript
|
|
891
|
-
import { $ } from 'sigpro';
|
|
892
|
-
|
|
893
|
-
const a = $(1);
|
|
894
|
-
const b = $(2);
|
|
895
|
-
|
|
896
|
-
// ❌ Avoid - can cause loops
|
|
897
|
-
$.effect(() => {
|
|
898
|
-
a(b()); // Writing to a while reading b
|
|
899
|
-
});
|
|
900
|
-
|
|
901
|
-
// ✅ Use computed signals instead
|
|
902
|
-
const sum = $(() => a() + b());
|
|
903
|
-
```
|
|
904
|
-
|
|
905
|
-
### 4. Use Conditional Logic Carefully
|
|
906
|
-
|
|
907
|
-
```javascript
|
|
908
|
-
// ❌ Condition affects dependency tracking
|
|
909
|
-
$.effect(() => {
|
|
910
|
-
if (condition()) {
|
|
911
|
-
console.log(a()); // Only tracks a when condition is true
|
|
912
|
-
}
|
|
913
|
-
});
|
|
914
|
-
|
|
915
|
-
// ✅ Track all dependencies explicitly
|
|
916
|
-
$.effect(() => {
|
|
917
|
-
const cond = condition(); // Track condition
|
|
918
|
-
if (cond) {
|
|
919
|
-
console.log(a()); // Track a
|
|
920
|
-
}
|
|
921
|
-
});
|
|
922
|
-
```
|
|
923
|
-
|
|
924
|
-
### 5. Memoize Expensive Computations
|
|
925
|
-
|
|
926
|
-
```javascript
|
|
927
|
-
import { $ } from 'sigpro';
|
|
928
|
-
|
|
929
|
-
const items = $([]);
|
|
930
|
-
|
|
931
|
-
// ❌ Expensive computation runs on every effect
|
|
932
|
-
$.effect(() => {
|
|
933
|
-
const total = items().reduce((sum, i) => sum + i.price, 0);
|
|
934
|
-
updateTotal(total);
|
|
935
|
-
});
|
|
936
|
-
|
|
937
|
-
// ✅ Memoize with computed signal
|
|
938
|
-
const total = $(() => items().reduce((sum, i) => sum + i.price, 0));
|
|
939
|
-
$.effect(() => updateTotal(total()));
|
|
940
|
-
```
|
|
941
|
-
|
|
942
|
-
## 🔍 Debugging Effects
|
|
943
|
-
|
|
944
|
-
### Logging Effect Runs
|
|
945
|
-
|
|
946
|
-
```javascript
|
|
947
|
-
import { $ } from 'sigpro';
|
|
948
|
-
|
|
949
|
-
const withLogging = (effectFn, name) => {
|
|
950
|
-
return $.effect(() => {
|
|
951
|
-
console.log(`[${name}] Running...`);
|
|
952
|
-
const start = performance.now();
|
|
953
|
-
|
|
954
|
-
const result = effectFn();
|
|
955
|
-
|
|
956
|
-
const duration = performance.now() - start;
|
|
957
|
-
console.log(`[${name}] Completed in ${duration.toFixed(2)}ms`);
|
|
958
|
-
|
|
959
|
-
return result;
|
|
960
|
-
});
|
|
961
|
-
};
|
|
962
|
-
|
|
963
|
-
// Usage
|
|
964
|
-
withLogging(() => {
|
|
965
|
-
console.log('Count:', count());
|
|
966
|
-
}, 'count-effect');
|
|
967
|
-
```
|
|
968
|
-
|
|
969
|
-
### Effect Inspector
|
|
970
|
-
|
|
971
|
-
```javascript
|
|
972
|
-
import { $ } from 'sigpro';
|
|
973
|
-
|
|
974
|
-
const createEffectInspector = () => {
|
|
975
|
-
const effects = new Map();
|
|
976
|
-
let id = 0;
|
|
977
|
-
|
|
978
|
-
const trackedEffect = (fn, name = `effect-${++id}`) => {
|
|
979
|
-
const info = {
|
|
980
|
-
name,
|
|
981
|
-
runs: 0,
|
|
982
|
-
lastRun: null,
|
|
983
|
-
duration: 0,
|
|
984
|
-
dependencies: new Set()
|
|
985
|
-
};
|
|
986
|
-
|
|
987
|
-
const wrapped = () => {
|
|
988
|
-
info.runs++;
|
|
989
|
-
info.lastRun = new Date();
|
|
990
|
-
const start = performance.now();
|
|
991
|
-
|
|
992
|
-
const result = fn();
|
|
993
|
-
|
|
994
|
-
info.duration = performance.now() - start;
|
|
995
|
-
return result;
|
|
996
|
-
};
|
|
997
|
-
|
|
998
|
-
const stop = $.effect(wrapped);
|
|
999
|
-
effects.set(stop, info);
|
|
1000
|
-
|
|
1001
|
-
return stop;
|
|
1002
|
-
};
|
|
1003
|
-
|
|
1004
|
-
const getReport = () => {
|
|
1005
|
-
const report = {};
|
|
1006
|
-
effects.forEach((info, stop) => {
|
|
1007
|
-
report[info.name] = {
|
|
1008
|
-
runs: info.runs,
|
|
1009
|
-
lastRun: info.lastRun,
|
|
1010
|
-
avgDuration: info.duration / info.runs
|
|
1011
|
-
};
|
|
1012
|
-
});
|
|
1013
|
-
return report;
|
|
1014
|
-
};
|
|
1015
|
-
|
|
1016
|
-
return { trackedEffect, getReport };
|
|
1017
|
-
};
|
|
1018
|
-
|
|
1019
|
-
// Usage
|
|
1020
|
-
const inspector = createEffectInspector();
|
|
1021
|
-
inspector.trackedEffect(() => {
|
|
1022
|
-
console.log('Count:', count());
|
|
1023
|
-
}, 'counter-effect');
|
|
1024
|
-
```
|
|
1025
|
-
|
|
1026
|
-
## 📊 Summary
|
|
1027
|
-
|
|
1028
|
-
| Feature | Description |
|
|
1029
|
-
|---------|-------------|
|
|
1030
|
-
| **Automatic Tracking** | Dependencies tracked automatically |
|
|
1031
|
-
| **Cleanup Functions** | Return function to clean up resources |
|
|
1032
|
-
| **Batch Updates** | Multiple changes batched in microtask |
|
|
1033
|
-
| **Manual Stop** | Can stop effects with returned function |
|
|
1034
|
-
| **Nested Effects** | Effects can contain other effects |
|
|
1035
|
-
| **Auto-cleanup** | Effects in pages/components auto-cleaned |
|
|
1036
|
-
|
|
1037
|
-
---
|
|
1038
|
-
|
|
1039
|
-
> **Pro Tip:** Effects are the perfect place for side effects like DOM updates, data fetching, and subscriptions. Keep them focused and always clean up resources!
|