@ersbeth/picoflow 0.2.3 → 1.0.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/.cursor/plans/update-js-e795d61b.plan.md +567 -0
- package/.gitlab-ci.yml +24 -0
- package/.vscode/settings.json +3 -3
- package/CHANGELOG.md +51 -0
- package/IMPLEMENTATION_GUIDE.md +1578 -0
- package/README.md +62 -25
- package/biome.json +32 -32
- package/dist/picoflow.js +557 -1099
- package/dist/types/advanced/array.d.ts +0 -6
- package/dist/types/advanced/array.d.ts.map +1 -1
- package/dist/types/advanced/index.d.ts +5 -5
- package/dist/types/advanced/index.d.ts.map +1 -1
- package/dist/types/advanced/map.d.ts +114 -23
- package/dist/types/advanced/map.d.ts.map +1 -1
- package/dist/types/advanced/resource.d.ts +51 -12
- package/dist/types/advanced/resource.d.ts.map +1 -1
- package/dist/types/advanced/resourceAsync.d.ts +28 -13
- package/dist/types/advanced/resourceAsync.d.ts.map +1 -1
- package/dist/types/advanced/stream.d.ts +74 -16
- package/dist/types/advanced/stream.d.ts.map +1 -1
- package/dist/types/advanced/streamAsync.d.ts +69 -15
- package/dist/types/advanced/streamAsync.d.ts.map +1 -1
- package/dist/types/basic/constant.d.ts +44 -16
- package/dist/types/basic/constant.d.ts.map +1 -1
- package/dist/types/basic/derivation.d.ts +73 -24
- package/dist/types/basic/derivation.d.ts.map +1 -1
- package/dist/types/basic/disposable.d.ts +65 -6
- package/dist/types/basic/disposable.d.ts.map +1 -1
- package/dist/types/basic/effect.d.ts +27 -16
- package/dist/types/basic/effect.d.ts.map +1 -1
- package/dist/types/basic/index.d.ts +7 -8
- package/dist/types/basic/index.d.ts.map +1 -1
- package/dist/types/basic/observable.d.ts +62 -13
- package/dist/types/basic/observable.d.ts.map +1 -1
- package/dist/types/basic/signal.d.ts +35 -6
- package/dist/types/basic/signal.d.ts.map +1 -1
- package/dist/types/basic/state.d.ts +25 -4
- package/dist/types/basic/state.d.ts.map +1 -1
- package/dist/types/basic/trackingContext.d.ts +33 -0
- package/dist/types/basic/trackingContext.d.ts.map +1 -0
- package/dist/types/creators.d.ts +271 -26
- package/dist/types/creators.d.ts.map +1 -1
- package/dist/types/index.d.ts +60 -7
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/solid/converters.d.ts +5 -5
- package/dist/types/solid/converters.d.ts.map +1 -1
- package/dist/types/solid/index.d.ts +2 -2
- package/dist/types/solid/index.d.ts.map +1 -1
- package/dist/types/solid/primitives.d.ts +96 -4
- package/dist/types/solid/primitives.d.ts.map +1 -1
- package/docs/.vitepress/config.mts +110 -0
- package/docs/api/classes/FlowArray.md +489 -0
- package/docs/api/classes/FlowConstant.md +350 -0
- package/docs/api/classes/FlowDerivation.md +334 -0
- package/docs/api/classes/FlowEffect.md +100 -0
- package/docs/api/classes/FlowMap.md +512 -0
- package/docs/api/classes/FlowObservable.md +306 -0
- package/docs/api/classes/FlowResource.md +380 -0
- package/docs/api/classes/FlowResourceAsync.md +362 -0
- package/docs/api/classes/FlowSignal.md +160 -0
- package/docs/api/classes/FlowState.md +368 -0
- package/docs/api/classes/FlowStream.md +367 -0
- package/docs/api/classes/FlowStreamAsync.md +364 -0
- package/docs/api/classes/SolidDerivation.md +75 -0
- package/docs/api/classes/SolidResource.md +91 -0
- package/docs/api/classes/SolidState.md +71 -0
- package/docs/api/classes/TrackingContext.md +33 -0
- package/docs/api/functions/array.md +58 -0
- package/docs/api/functions/constant.md +45 -0
- package/docs/api/functions/derivation.md +53 -0
- package/docs/api/functions/effect.md +49 -0
- package/docs/api/functions/from.md +220 -0
- package/docs/api/functions/isDisposable.md +49 -0
- package/docs/api/functions/map.md +57 -0
- package/docs/api/functions/resource.md +52 -0
- package/docs/api/functions/resourceAsync.md +50 -0
- package/docs/api/functions/signal.md +36 -0
- package/docs/api/functions/state.md +47 -0
- package/docs/api/functions/stream.md +53 -0
- package/docs/api/functions/streamAsync.md +50 -0
- package/docs/api/index.md +118 -0
- package/docs/api/interfaces/FlowDisposable.md +65 -0
- package/docs/api/interfaces/SolidObservable.md +19 -0
- package/docs/api/type-aliases/FlowArrayAction.md +49 -0
- package/docs/api/type-aliases/FlowStreamDisposer.md +15 -0
- package/docs/api/type-aliases/FlowStreamSetter.md +27 -0
- package/docs/api/type-aliases/FlowStreamUpdater.md +32 -0
- package/docs/api/type-aliases/NotPromise.md +18 -0
- package/docs/api/type-aliases/SolidGetter.md +17 -0
- package/docs/api/typedoc-sidebar.json +1 -0
- package/docs/examples/examples.md +2313 -0
- package/docs/examples/patterns.md +649 -0
- package/docs/guide/advanced/disposal.md +426 -0
- package/docs/guide/advanced/solidjs.md +221 -0
- package/docs/guide/advanced/upgrading.md +464 -0
- package/docs/guide/introduction/concepts.md +56 -0
- package/docs/guide/introduction/conventions.md +61 -0
- package/docs/guide/introduction/getting-started.md +134 -0
- package/docs/guide/introduction/lifecycle.md +371 -0
- package/docs/guide/primitives/array.md +400 -0
- package/docs/guide/primitives/constant.md +380 -0
- package/docs/guide/primitives/derivations.md +348 -0
- package/docs/guide/primitives/effects.md +458 -0
- package/docs/guide/primitives/map.md +387 -0
- package/docs/guide/primitives/overview.md +175 -0
- package/docs/guide/primitives/resources.md +858 -0
- package/docs/guide/primitives/signal.md +259 -0
- package/docs/guide/primitives/state.md +368 -0
- package/docs/guide/primitives/streams.md +931 -0
- package/docs/index.md +47 -0
- package/docs/public/logo.svg +1 -0
- package/package.json +57 -41
- package/src/advanced/array.ts +208 -210
- package/src/advanced/index.ts +7 -7
- package/src/advanced/map.ts +178 -68
- package/src/advanced/resource.ts +87 -43
- package/src/advanced/resourceAsync.ts +62 -42
- package/src/advanced/stream.ts +113 -50
- package/src/advanced/streamAsync.ts +120 -61
- package/src/basic/constant.ts +82 -49
- package/src/basic/derivation.ts +128 -84
- package/src/basic/disposable.ts +74 -15
- package/src/basic/effect.ts +85 -77
- package/src/basic/index.ts +7 -8
- package/src/basic/observable.ts +94 -36
- package/src/basic/signal.ts +133 -105
- package/src/basic/state.ts +46 -25
- package/src/basic/trackingContext.ts +45 -0
- package/src/creators.ts +297 -54
- package/src/index.ts +96 -43
- package/src/solid/converters.ts +186 -67
- package/src/solid/index.ts +8 -2
- package/src/solid/primitives.ts +167 -65
- package/test/array.test.ts +592 -612
- package/test/constant.test.ts +31 -33
- package/test/derivation.test.ts +531 -536
- package/test/effect.test.ts +21 -21
- package/test/map.test.ts +233 -137
- package/test/resource.test.ts +119 -121
- package/test/resourceAsync.test.ts +98 -100
- package/test/signal.test.ts +51 -55
- package/test/state.test.ts +186 -168
- package/test/stream.test.ts +189 -189
- package/test/streamAsync.test.ts +186 -186
- package/tsconfig.json +19 -18
- package/typedoc.json +37 -0
- package/vite.config.ts +23 -20
- package/vitest.config.ts +7 -7
- package/api/doc/index.md +0 -31
- package/api/doc/picoflow.array.md +0 -55
- package/api/doc/picoflow.constant.md +0 -55
- package/api/doc/picoflow.derivation.md +0 -55
- package/api/doc/picoflow.effect.md +0 -55
- package/api/doc/picoflow.flowarray._constructor_.md +0 -49
- package/api/doc/picoflow.flowarray._lastaction.md +0 -13
- package/api/doc/picoflow.flowarray.clear.md +0 -17
- package/api/doc/picoflow.flowarray.dispose.md +0 -55
- package/api/doc/picoflow.flowarray.get.md +0 -19
- package/api/doc/picoflow.flowarray.length.md +0 -13
- package/api/doc/picoflow.flowarray.md +0 -273
- package/api/doc/picoflow.flowarray.pop.md +0 -17
- package/api/doc/picoflow.flowarray.push.md +0 -53
- package/api/doc/picoflow.flowarray.set.md +0 -53
- package/api/doc/picoflow.flowarray.setitem.md +0 -69
- package/api/doc/picoflow.flowarray.shift.md +0 -17
- package/api/doc/picoflow.flowarray.splice.md +0 -85
- package/api/doc/picoflow.flowarray.unshift.md +0 -53
- package/api/doc/picoflow.flowarrayaction.md +0 -37
- package/api/doc/picoflow.flowconstant._constructor_.md +0 -49
- package/api/doc/picoflow.flowconstant.get.md +0 -25
- package/api/doc/picoflow.flowconstant.md +0 -88
- package/api/doc/picoflow.flowderivation._constructor_.md +0 -49
- package/api/doc/picoflow.flowderivation.get.md +0 -23
- package/api/doc/picoflow.flowderivation.md +0 -86
- package/api/doc/picoflow.flowdisposable.dispose.md +0 -55
- package/api/doc/picoflow.flowdisposable.md +0 -43
- package/api/doc/picoflow.floweffect._constructor_.md +0 -54
- package/api/doc/picoflow.floweffect.dispose.md +0 -21
- package/api/doc/picoflow.floweffect.disposed.md +0 -13
- package/api/doc/picoflow.floweffect.md +0 -131
- package/api/doc/picoflow.flowgetter.md +0 -15
- package/api/doc/picoflow.flowmap._lastdeleted.md +0 -21
- package/api/doc/picoflow.flowmap._lastset.md +0 -21
- package/api/doc/picoflow.flowmap.delete.md +0 -61
- package/api/doc/picoflow.flowmap.md +0 -133
- package/api/doc/picoflow.flowmap.setat.md +0 -77
- package/api/doc/picoflow.flowobservable.get.md +0 -19
- package/api/doc/picoflow.flowobservable.md +0 -68
- package/api/doc/picoflow.flowobservable.subscribe.md +0 -55
- package/api/doc/picoflow.flowresource._constructor_.md +0 -49
- package/api/doc/picoflow.flowresource.fetch.md +0 -27
- package/api/doc/picoflow.flowresource.get.md +0 -23
- package/api/doc/picoflow.flowresource.md +0 -100
- package/api/doc/picoflow.flowresourceasync._constructor_.md +0 -49
- package/api/doc/picoflow.flowresourceasync.fetch.md +0 -27
- package/api/doc/picoflow.flowresourceasync.get.md +0 -23
- package/api/doc/picoflow.flowresourceasync.md +0 -100
- package/api/doc/picoflow.flowsignal.dispose.md +0 -59
- package/api/doc/picoflow.flowsignal.disposed.md +0 -18
- package/api/doc/picoflow.flowsignal.md +0 -112
- package/api/doc/picoflow.flowsignal.trigger.md +0 -21
- package/api/doc/picoflow.flowstate.md +0 -52
- package/api/doc/picoflow.flowstate.set.md +0 -61
- package/api/doc/picoflow.flowstream._constructor_.md +0 -49
- package/api/doc/picoflow.flowstream.dispose.md +0 -21
- package/api/doc/picoflow.flowstream.get.md +0 -23
- package/api/doc/picoflow.flowstream.md +0 -100
- package/api/doc/picoflow.flowstreamasync._constructor_.md +0 -54
- package/api/doc/picoflow.flowstreamasync.dispose.md +0 -21
- package/api/doc/picoflow.flowstreamasync.get.md +0 -23
- package/api/doc/picoflow.flowstreamasync.md +0 -100
- package/api/doc/picoflow.flowstreamdisposer.md +0 -13
- package/api/doc/picoflow.flowstreamsetter.md +0 -13
- package/api/doc/picoflow.flowstreamupdater.md +0 -19
- package/api/doc/picoflow.flowwatcher.md +0 -15
- package/api/doc/picoflow.from.md +0 -55
- package/api/doc/picoflow.from_1.md +0 -55
- package/api/doc/picoflow.from_2.md +0 -55
- package/api/doc/picoflow.from_3.md +0 -55
- package/api/doc/picoflow.from_4.md +0 -55
- package/api/doc/picoflow.from_5.md +0 -55
- package/api/doc/picoflow.isdisposable.md +0 -55
- package/api/doc/picoflow.map.md +0 -59
- package/api/doc/picoflow.md +0 -544
- package/api/doc/picoflow.resource.md +0 -55
- package/api/doc/picoflow.resourceasync.md +0 -55
- package/api/doc/picoflow.signal.md +0 -19
- package/api/doc/picoflow.solidderivation._constructor_.md +0 -49
- package/api/doc/picoflow.solidderivation.get.md +0 -13
- package/api/doc/picoflow.solidderivation.md +0 -94
- package/api/doc/picoflow.solidgetter.md +0 -13
- package/api/doc/picoflow.solidobservable.get.md +0 -13
- package/api/doc/picoflow.solidobservable.md +0 -57
- package/api/doc/picoflow.solidresource._constructor_.md +0 -49
- package/api/doc/picoflow.solidresource.get.md +0 -13
- package/api/doc/picoflow.solidresource.latest.md +0 -13
- package/api/doc/picoflow.solidresource.md +0 -157
- package/api/doc/picoflow.solidresource.refetch.md +0 -13
- package/api/doc/picoflow.solidresource.state.md +0 -13
- package/api/doc/picoflow.solidstate._constructor_.md +0 -49
- package/api/doc/picoflow.solidstate.get.md +0 -13
- package/api/doc/picoflow.solidstate.md +0 -115
- package/api/doc/picoflow.solidstate.set.md +0 -13
- package/api/doc/picoflow.state.md +0 -55
- package/api/doc/picoflow.stream.md +0 -55
- package/api/doc/picoflow.streamasync.md +0 -55
- package/api/picoflow.public.api.md +0 -244
- package/api-extractor.json +0 -61
|
@@ -0,0 +1,458 @@
|
|
|
1
|
+
# Effects
|
|
2
|
+
|
|
3
|
+
Effects are how you perform **side effects** in response to reactive changes. They're the bridge between your reactive data and the outside world - updating the DOM, making API calls, saving to localStorage, and more.
|
|
4
|
+
|
|
5
|
+
Imagine a light switch: when you flip it, the light turns on. Effects work the same way in reactive programming - when your data changes, effects automatically run to update the world around them.
|
|
6
|
+
|
|
7
|
+
### Key Characteristics
|
|
8
|
+
|
|
9
|
+
- **Immediate execution**: Runs synchronously when created
|
|
10
|
+
- **Automatic re-execution**: Re-runs when dependencies change
|
|
11
|
+
- **Dynamic dependency tracking**: Dependencies adapt at runtime
|
|
12
|
+
- **Side effects allowed**: Designed for DOM, API, logging, etc.
|
|
13
|
+
- **Disposal support**: Clean up resources when no longer needed
|
|
14
|
+
|
|
15
|
+
## When to Use Effects
|
|
16
|
+
|
|
17
|
+
Use effects when you need to:
|
|
18
|
+
|
|
19
|
+
- ✅ Perform side effects (DOM updates, API calls, localStorage)
|
|
20
|
+
- ✅ React to changes in reactive values
|
|
21
|
+
- ✅ Coordinate with external systems
|
|
22
|
+
- ✅ Set up subscriptions or event listeners
|
|
23
|
+
- ✅ Log or debug reactive changes
|
|
24
|
+
|
|
25
|
+
Don't use effects when:
|
|
26
|
+
|
|
27
|
+
- ❌ Computing derived values (use derivations instead)
|
|
28
|
+
- ❌ The value needs to be used elsewhere reactively (use derivations)
|
|
29
|
+
- ❌ You only need to run code once (use regular functions)
|
|
30
|
+
|
|
31
|
+
## Creating Effects
|
|
32
|
+
|
|
33
|
+
Creating an effect is straightforward:
|
|
34
|
+
|
|
35
|
+
```typescript
|
|
36
|
+
import { effect } from '@ersbeth/picoflow'
|
|
37
|
+
|
|
38
|
+
effect((t) => {
|
|
39
|
+
// Your side effect code here
|
|
40
|
+
// Use t to track dependencies
|
|
41
|
+
})
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
The parameter `t` is the **TrackingContext** - your tool for creating dependencies.
|
|
45
|
+
|
|
46
|
+
## Using Effects
|
|
47
|
+
|
|
48
|
+
Effects have several ways to interact with reactive values and manage their lifecycle.
|
|
49
|
+
|
|
50
|
+
### Tracking with `.get(t)` (Reactive)
|
|
51
|
+
|
|
52
|
+
Use `.get(t)` to create dependencies - the effect will re-run when the value changes:
|
|
53
|
+
|
|
54
|
+
```typescript
|
|
55
|
+
const $username = state('Alice')
|
|
56
|
+
|
|
57
|
+
effect((t) => {
|
|
58
|
+
const name = $username.get(t) // Creates dependency
|
|
59
|
+
document.getElementById('username').textContent = name
|
|
60
|
+
})
|
|
61
|
+
|
|
62
|
+
$username.set('Bob') // Effect re-runs, DOM updates
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
### Non-Reactive Reads with `.pick()`
|
|
66
|
+
|
|
67
|
+
Use `.pick()` to read the current value without creating a dependency:
|
|
68
|
+
|
|
69
|
+
```typescript
|
|
70
|
+
const $activeUserId = state(1)
|
|
71
|
+
const $userDatabase = state({ /* large object */ })
|
|
72
|
+
|
|
73
|
+
effect((t) => {
|
|
74
|
+
const activeId = $activeUserId.get(t) // Reactive
|
|
75
|
+
const database = $userDatabase.pick() // Snapshot
|
|
76
|
+
|
|
77
|
+
const user = database[activeId]
|
|
78
|
+
console.log('Active user:', user)
|
|
79
|
+
})
|
|
80
|
+
|
|
81
|
+
// Only changes to $activeUserId trigger the effect
|
|
82
|
+
// Changes to $userDatabase are ignored
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
### Watching Signals with `.watch(t)`
|
|
86
|
+
|
|
87
|
+
Signals are special - they don't hold values, so use `.watch(t)` to track them:
|
|
88
|
+
|
|
89
|
+
```typescript
|
|
90
|
+
import { signal, effect } from '@ersbeth/picoflow'
|
|
91
|
+
|
|
92
|
+
const $refresh = signal()
|
|
93
|
+
|
|
94
|
+
effect((t) => {
|
|
95
|
+
$refresh.watch(t) // React to signal triggers
|
|
96
|
+
console.log('Refreshing data...')
|
|
97
|
+
fetchData()
|
|
98
|
+
})
|
|
99
|
+
|
|
100
|
+
$refresh.trigger() // Effect runs
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
### Multiple Dependencies
|
|
104
|
+
|
|
105
|
+
Effects can track multiple reactive values:
|
|
106
|
+
|
|
107
|
+
```typescript
|
|
108
|
+
const $firstName = state('Alice')
|
|
109
|
+
const $lastName = state('Smith')
|
|
110
|
+
|
|
111
|
+
effect((t) => {
|
|
112
|
+
const first = $firstName.get(t)
|
|
113
|
+
const last = $lastName.get(t)
|
|
114
|
+
console.log('Full name:', first, last)
|
|
115
|
+
})
|
|
116
|
+
|
|
117
|
+
// Changes to EITHER trigger the effect
|
|
118
|
+
$firstName.set('Bob') // Effect runs
|
|
119
|
+
$lastName.set('Jones') // Effect runs
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
### Conditional Dependencies
|
|
123
|
+
|
|
124
|
+
Dependencies can be conditional - they're re-evaluated on each execution:
|
|
125
|
+
|
|
126
|
+
```typescript
|
|
127
|
+
const $showDetails = state(false)
|
|
128
|
+
const $details = state({ info: 'secret' })
|
|
129
|
+
|
|
130
|
+
effect((t) => {
|
|
131
|
+
if ($showDetails.get(t)) {
|
|
132
|
+
// Only depends on $details when $showDetails is true
|
|
133
|
+
console.log('Details:', $details.get(t))
|
|
134
|
+
} else {
|
|
135
|
+
console.log('Details hidden')
|
|
136
|
+
}
|
|
137
|
+
})
|
|
138
|
+
|
|
139
|
+
// When $showDetails is false, changes to $details don't trigger the effect
|
|
140
|
+
$details.set({ info: 'new secret' }) // Effect doesn't run
|
|
141
|
+
|
|
142
|
+
// But once $showDetails is true...
|
|
143
|
+
$showDetails.set(true) // Effect runs and now tracks $details
|
|
144
|
+
$details.set({ info: 'updated' }) // Now this triggers the effect!
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
### Disposing with `.dispose()`
|
|
148
|
+
|
|
149
|
+
Effects run indefinitely unless disposed. Always clean them up when no longer needed:
|
|
150
|
+
|
|
151
|
+
```typescript
|
|
152
|
+
const $count = state(0)
|
|
153
|
+
|
|
154
|
+
// Create effect
|
|
155
|
+
const fx = effect((t) => {
|
|
156
|
+
console.log($count.get(t))
|
|
157
|
+
})
|
|
158
|
+
|
|
159
|
+
// Later... stop the effect
|
|
160
|
+
fx.dispose()
|
|
161
|
+
|
|
162
|
+
// After disposal, the effect won't run anymore
|
|
163
|
+
$count.set(10) // No console log
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
**Always dispose effects when:**
|
|
167
|
+
- Component unmounts (in UI frameworks)
|
|
168
|
+
- Feature is disabled
|
|
169
|
+
- User navigates away
|
|
170
|
+
- App shuts down
|
|
171
|
+
|
|
172
|
+
**Memory leak example:**
|
|
173
|
+
|
|
174
|
+
```typescript
|
|
175
|
+
// ❌ Memory leak
|
|
176
|
+
function createUserPanel(userId: number) {
|
|
177
|
+
const $user = state(getUserData(userId))
|
|
178
|
+
|
|
179
|
+
effect((t) => {
|
|
180
|
+
updateUI($user.get(t))
|
|
181
|
+
})
|
|
182
|
+
// Effect never disposed! ⚠️
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
// Called 100 times = 100 effects still running!
|
|
186
|
+
|
|
187
|
+
// ✅ Proper cleanup
|
|
188
|
+
function createUserPanel(userId: number) {
|
|
189
|
+
const $user = state(getUserData(userId))
|
|
190
|
+
|
|
191
|
+
const fx = effect((t) => {
|
|
192
|
+
updateUI($user.get(t))
|
|
193
|
+
})
|
|
194
|
+
|
|
195
|
+
return {
|
|
196
|
+
dispose: () => {
|
|
197
|
+
fx.dispose()
|
|
198
|
+
$user.dispose()
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
## Lifecycle
|
|
205
|
+
|
|
206
|
+
Effects execute immediately when created, track their dependencies, and re-execute when any dependency changes. Understanding this lifecycle is key to writing efficient reactive code.
|
|
207
|
+
|
|
208
|
+
When you create an effect, it runs **synchronously** - not on the next tick, but right away. This allows effects to establish initial dependencies and set up initial side effects. When a dependency changes, the effect re-executes synchronously.
|
|
209
|
+
|
|
210
|
+
```mermaid
|
|
211
|
+
sequenceDiagram
|
|
212
|
+
participant User
|
|
213
|
+
participant $count as $count (State)
|
|
214
|
+
participant Effect
|
|
215
|
+
|
|
216
|
+
Note over User,Effect: 1. Creation Phase
|
|
217
|
+
User->>Effect: Create effect((t) => ...)
|
|
218
|
+
activate Effect
|
|
219
|
+
Note over Effect: Execute immediately
|
|
220
|
+
|
|
221
|
+
Effect->>$count: get(t)
|
|
222
|
+
activate $count
|
|
223
|
+
Note over $count: Register Effect as dependent
|
|
224
|
+
$count-->>Effect: 0
|
|
225
|
+
deactivate $count
|
|
226
|
+
|
|
227
|
+
Note over Effect: console.log("Count: 0")
|
|
228
|
+
Note over Effect: Track: depends on $count
|
|
229
|
+
deactivate Effect
|
|
230
|
+
|
|
231
|
+
Note over User,Effect: 2. Change Phase
|
|
232
|
+
User->>$count: set(5)
|
|
233
|
+
activate $count
|
|
234
|
+
Note over $count: Value changed: 0 → 5
|
|
235
|
+
|
|
236
|
+
$count->>Effect: Notify & schedule
|
|
237
|
+
deactivate $count
|
|
238
|
+
|
|
239
|
+
activate Effect
|
|
240
|
+
Note over Effect: Clear old dependencies<br/>Execute function
|
|
241
|
+
|
|
242
|
+
Effect->>$count: get(t)
|
|
243
|
+
activate $count
|
|
244
|
+
Note over $count: Register Effect as dependent
|
|
245
|
+
$count-->>Effect: 5
|
|
246
|
+
deactivate $count
|
|
247
|
+
|
|
248
|
+
Note over Effect: console.log("Count: 5")
|
|
249
|
+
Note over Effect: Track: depends on $count
|
|
250
|
+
deactivate Effect
|
|
251
|
+
|
|
252
|
+
Note over User,Effect: 3. Disposal Phase
|
|
253
|
+
User->>Effect: dispose()
|
|
254
|
+
activate Effect
|
|
255
|
+
Note over Effect: Clear dependencies<br/>Unregister from $count
|
|
256
|
+
Note over Effect: Mark as disposed
|
|
257
|
+
deactivate Effect
|
|
258
|
+
|
|
259
|
+
User->>$count: set(10)
|
|
260
|
+
activate $count
|
|
261
|
+
Note over $count: Value changed<br/>No dependents to notify
|
|
262
|
+
deactivate $count
|
|
263
|
+
Note over Effect: (Does not run - disposed)
|
|
264
|
+
```
|
|
265
|
+
|
|
266
|
+
### Key Lifecycle Points
|
|
267
|
+
|
|
268
|
+
1. **Creation & Immediate Execution**: The effect function runs synchronously during creation
|
|
269
|
+
2. **Dependency Registration**: Reactive reads (`.get(t)`) register the effect as a dependent
|
|
270
|
+
3. **Change Notification**: When dependencies change, the effect is scheduled to re-run
|
|
271
|
+
4. **Re-execution**: Effect clears old dependencies, runs again, re-registers new dependencies
|
|
272
|
+
5. **Disposal**: Clears all dependencies, unregisters from all reactive values, prevents future execution
|
|
273
|
+
|
|
274
|
+
This immediate, synchronous execution model means:
|
|
275
|
+
- **Effects establish dependencies on first run** (no waiting)
|
|
276
|
+
- **Changes trigger re-execution immediately** (synchronous)
|
|
277
|
+
- **Dynamic dependencies adapt automatically** (conditional tracking)
|
|
278
|
+
|
|
279
|
+
## Best Practices
|
|
280
|
+
|
|
281
|
+
### Keep Effects Focused
|
|
282
|
+
|
|
283
|
+
Each effect should do one thing:
|
|
284
|
+
|
|
285
|
+
```typescript
|
|
286
|
+
// ❌ Doing too much
|
|
287
|
+
effect((t) => {
|
|
288
|
+
updateDOM($count.get(t))
|
|
289
|
+
saveToStorage($count.get(t))
|
|
290
|
+
sendAnalytics($count.get(t))
|
|
291
|
+
updateTitle($count.get(t))
|
|
292
|
+
})
|
|
293
|
+
|
|
294
|
+
// ✅ Separate concerns
|
|
295
|
+
effect((t) => updateDOM($count.get(t)))
|
|
296
|
+
effect((t) => saveToStorage($count.get(t)))
|
|
297
|
+
effect((t) => sendAnalytics($count.get(t)))
|
|
298
|
+
effect((t) => updateTitle($count.get(t)))
|
|
299
|
+
```
|
|
300
|
+
|
|
301
|
+
**Why?** Easier to debug, test, and disable individual effects.
|
|
302
|
+
|
|
303
|
+
### Avoid Creating Effects Inside Effects
|
|
304
|
+
|
|
305
|
+
```typescript
|
|
306
|
+
// ❌ Bad - creates new effects on every run
|
|
307
|
+
effect((t) => {
|
|
308
|
+
const count = $count.get(t)
|
|
309
|
+
effect((t) => {
|
|
310
|
+
console.log('Nested:', count) // Memory leak!
|
|
311
|
+
})
|
|
312
|
+
})
|
|
313
|
+
|
|
314
|
+
// ✅ Good - create effects at the top level
|
|
315
|
+
effect((t) => {
|
|
316
|
+
const count = $count.get(t)
|
|
317
|
+
console.log('Count:', count)
|
|
318
|
+
})
|
|
319
|
+
```
|
|
320
|
+
|
|
321
|
+
### Handle Errors
|
|
322
|
+
|
|
323
|
+
Wrap risky operations in try-catch:
|
|
324
|
+
|
|
325
|
+
```typescript
|
|
326
|
+
effect((t) => {
|
|
327
|
+
const data = $data.get(t)
|
|
328
|
+
|
|
329
|
+
try {
|
|
330
|
+
updateDOM(data)
|
|
331
|
+
} catch (error) {
|
|
332
|
+
console.error('Failed to update DOM:', error)
|
|
333
|
+
}
|
|
334
|
+
})
|
|
335
|
+
```
|
|
336
|
+
|
|
337
|
+
### Be Careful with Infinite Loops
|
|
338
|
+
|
|
339
|
+
Don't update dependencies inside the same effect:
|
|
340
|
+
|
|
341
|
+
```typescript
|
|
342
|
+
const $count = state(0)
|
|
343
|
+
|
|
344
|
+
// ❌ Infinite loop!
|
|
345
|
+
effect((t) => {
|
|
346
|
+
const count = $count.get(t)
|
|
347
|
+
$count.set(count + 1) // Triggers itself!
|
|
348
|
+
})
|
|
349
|
+
|
|
350
|
+
// ✅ Use a different state
|
|
351
|
+
effect((t) => {
|
|
352
|
+
const count = $count.get(t)
|
|
353
|
+
$doubled.set(count * 2) // Updates different state
|
|
354
|
+
})
|
|
355
|
+
```
|
|
356
|
+
|
|
357
|
+
### Use Signals for Event-Only Tracking
|
|
358
|
+
|
|
359
|
+
When you don't need a value, just an event:
|
|
360
|
+
|
|
361
|
+
```typescript
|
|
362
|
+
const $refresh = signal()
|
|
363
|
+
|
|
364
|
+
effect((t) => {
|
|
365
|
+
$refresh.watch(t) // Track signal without reading a value
|
|
366
|
+
fetchData()
|
|
367
|
+
})
|
|
368
|
+
|
|
369
|
+
// Trigger refresh
|
|
370
|
+
$refresh.trigger()
|
|
371
|
+
```
|
|
372
|
+
|
|
373
|
+
## Common Pitfalls
|
|
374
|
+
|
|
375
|
+
### Forgetting to Use `.get(t)`
|
|
376
|
+
|
|
377
|
+
**Problem**: Using `.pick()` instead of `.get(t)` breaks reactivity.
|
|
378
|
+
|
|
379
|
+
```typescript
|
|
380
|
+
// ❌ No dependency created - effect runs only once
|
|
381
|
+
effect((t) => {
|
|
382
|
+
console.log($count.pick()) // Using .pick() instead of .get(t)
|
|
383
|
+
})
|
|
384
|
+
|
|
385
|
+
$count.set(10) // Effect won't run
|
|
386
|
+
```
|
|
387
|
+
|
|
388
|
+
**Solution**: Always use `.get(t)` to create dependencies:
|
|
389
|
+
|
|
390
|
+
```typescript
|
|
391
|
+
// ✅ Correct
|
|
392
|
+
effect((t) => {
|
|
393
|
+
console.log($count.get(t))
|
|
394
|
+
})
|
|
395
|
+
|
|
396
|
+
$count.set(10) // Effect runs
|
|
397
|
+
```
|
|
398
|
+
|
|
399
|
+
### Not Disposing Effects
|
|
400
|
+
|
|
401
|
+
**Problem**: Effects continue running even when no longer needed.
|
|
402
|
+
|
|
403
|
+
```typescript
|
|
404
|
+
// ❌ Memory leak in component lifecycle
|
|
405
|
+
function MyComponent() {
|
|
406
|
+
effect((t) => {
|
|
407
|
+
updateView($data.get(t))
|
|
408
|
+
})
|
|
409
|
+
// Never disposed!
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
// Each component creation adds another effect that never stops
|
|
413
|
+
```
|
|
414
|
+
|
|
415
|
+
**Solution**: Always dispose effects when done:
|
|
416
|
+
|
|
417
|
+
```typescript
|
|
418
|
+
// ✅ Proper cleanup
|
|
419
|
+
function MyComponent() {
|
|
420
|
+
const fx = effect((t) => {
|
|
421
|
+
updateView($data.get(t))
|
|
422
|
+
})
|
|
423
|
+
|
|
424
|
+
return {
|
|
425
|
+
cleanup: () => fx.dispose()
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
```
|
|
429
|
+
|
|
430
|
+
### Async Operations Without Care
|
|
431
|
+
|
|
432
|
+
**Problem**: Race conditions when using async operations in effects.
|
|
433
|
+
|
|
434
|
+
```typescript
|
|
435
|
+
// ⚠️ Race condition possible
|
|
436
|
+
effect(async (t) => {
|
|
437
|
+
const id = $userId.get(t)
|
|
438
|
+
const data = await fetchUser(id) // If $userId changes during fetch...
|
|
439
|
+
displayUser(data) // ...we might display wrong user!
|
|
440
|
+
})
|
|
441
|
+
```
|
|
442
|
+
|
|
443
|
+
**Solution**: Check if values are still relevant after async operations:
|
|
444
|
+
|
|
445
|
+
```typescript
|
|
446
|
+
// ✅ Better - check if still relevant
|
|
447
|
+
effect(async (t) => {
|
|
448
|
+
const id = $userId.get(t)
|
|
449
|
+
const data = await fetchUser(id)
|
|
450
|
+
|
|
451
|
+
// Verify ID didn't change during fetch
|
|
452
|
+
if ($userId.pick() === id) {
|
|
453
|
+
displayUser(data)
|
|
454
|
+
}
|
|
455
|
+
})
|
|
456
|
+
```
|
|
457
|
+
|
|
458
|
+
**Why this matters**: Between starting an async operation and its completion, your reactive values might change. Always verify the context is still valid before applying results.
|