mutts 1.0.2 → 1.0.3
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 +14 -6
- package/dist/chunks/{_tslib-C-cuVLvZ.js → _tslib-BgjropY9.js} +9 -1
- package/dist/chunks/_tslib-BgjropY9.js.map +1 -0
- package/dist/chunks/{_tslib-CMEnd0VE.esm.js → _tslib-Mzh1rNsX.esm.js} +9 -2
- package/dist/chunks/_tslib-Mzh1rNsX.esm.js.map +1 -0
- package/dist/chunks/{decorator-D4DU97Zg.js → decorator-DLvrD0UF.js} +42 -19
- package/dist/chunks/decorator-DLvrD0UF.js.map +1 -0
- package/dist/chunks/{decorator-GnHw1Az7.esm.js → decorator-DqiszP7i.esm.js} +42 -19
- package/dist/chunks/decorator-DqiszP7i.esm.js.map +1 -0
- package/dist/chunks/index-DzUDtFc7.esm.js +4841 -0
- package/dist/chunks/index-DzUDtFc7.esm.js.map +1 -0
- package/dist/chunks/index-HNVqPzjz.js +4891 -0
- package/dist/chunks/index-HNVqPzjz.js.map +1 -0
- package/dist/decorator.esm.js +1 -1
- package/dist/decorator.js +1 -1
- package/dist/destroyable.d.ts +1 -1
- package/dist/destroyable.esm.js +1 -1
- package/dist/destroyable.esm.js.map +1 -1
- package/dist/destroyable.js +1 -1
- package/dist/destroyable.js.map +1 -1
- package/dist/devtools/devtools.html +9 -0
- package/dist/devtools/devtools.js +5 -0
- package/dist/devtools/devtools.js.map +1 -0
- package/dist/devtools/manifest.json +8 -0
- package/dist/devtools/panel.css +72 -0
- package/dist/devtools/panel.html +31 -0
- package/dist/devtools/panel.js +13048 -0
- package/dist/devtools/panel.js.map +1 -0
- package/dist/eventful.esm.js +1 -1
- package/dist/eventful.js +1 -1
- package/dist/index.d.ts +18 -63
- package/dist/index.esm.js +4 -4
- package/dist/index.js +36 -11
- package/dist/index.js.map +1 -1
- package/dist/indexable.d.ts +187 -1
- package/dist/indexable.esm.js +197 -3
- package/dist/indexable.esm.js.map +1 -1
- package/dist/indexable.js +198 -2
- package/dist/indexable.js.map +1 -1
- package/dist/mutts.umd.js +1 -1
- package/dist/mutts.umd.js.map +1 -1
- package/dist/mutts.umd.min.js +1 -1
- package/dist/mutts.umd.min.js.map +1 -1
- package/dist/promiseChain.esm.js.map +1 -1
- package/dist/promiseChain.js.map +1 -1
- package/dist/reactive.d.ts +601 -97
- package/dist/reactive.esm.js +3 -3
- package/dist/reactive.js +31 -10
- package/dist/reactive.js.map +1 -1
- package/dist/std-decorators.esm.js +1 -1
- package/dist/std-decorators.js +1 -1
- package/docs/ai/api-reference.md +133 -0
- package/docs/ai/manual.md +105 -0
- package/docs/iterableWeak.md +646 -0
- package/docs/reactive/advanced.md +1280 -0
- package/docs/reactive/collections.md +767 -0
- package/docs/reactive/core.md +973 -0
- package/docs/reactive.md +21 -9545
- package/package.json +18 -5
- package/src/decorator.ts +266 -0
- package/src/destroyable.ts +199 -0
- package/src/eventful.ts +77 -0
- package/src/index.d.ts +9 -0
- package/src/index.ts +9 -0
- package/src/indexable.ts +484 -0
- package/src/introspection.ts +59 -0
- package/src/iterableWeak.ts +233 -0
- package/src/mixins.ts +123 -0
- package/src/promiseChain.ts +110 -0
- package/src/reactive/array.ts +414 -0
- package/src/reactive/change.ts +134 -0
- package/src/reactive/debug.ts +517 -0
- package/src/reactive/deep-touch.ts +268 -0
- package/src/reactive/deep-watch-state.ts +82 -0
- package/src/reactive/deep-watch.ts +168 -0
- package/src/reactive/effect-context.ts +94 -0
- package/src/reactive/effects.ts +1333 -0
- package/src/reactive/index.ts +75 -0
- package/src/reactive/interface.ts +223 -0
- package/src/reactive/map.ts +171 -0
- package/src/reactive/mapped.ts +130 -0
- package/src/reactive/memoize.ts +107 -0
- package/src/reactive/non-reactive-state.ts +49 -0
- package/src/reactive/non-reactive.ts +43 -0
- package/src/reactive/project.project.md +93 -0
- package/src/reactive/project.ts +335 -0
- package/src/reactive/proxy-state.ts +27 -0
- package/src/reactive/proxy.ts +285 -0
- package/src/reactive/record.ts +196 -0
- package/src/reactive/register.ts +421 -0
- package/src/reactive/set.ts +144 -0
- package/src/reactive/tracking.ts +101 -0
- package/src/reactive/types.ts +358 -0
- package/src/reactive/zone.ts +208 -0
- package/src/std-decorators.ts +217 -0
- package/src/utils.ts +117 -0
- package/dist/chunks/_tslib-C-cuVLvZ.js.map +0 -1
- package/dist/chunks/_tslib-CMEnd0VE.esm.js.map +0 -1
- package/dist/chunks/decorator-D4DU97Zg.js.map +0 -1
- package/dist/chunks/decorator-GnHw1Az7.esm.js.map +0 -1
- package/dist/chunks/index-DBScoeCX.esm.js +0 -1960
- package/dist/chunks/index-DBScoeCX.esm.js.map +0 -1
- package/dist/chunks/index-DOTmXL89.js +0 -1983
- package/dist/chunks/index-DOTmXL89.js.map +0 -1
|
@@ -0,0 +1,973 @@
|
|
|
1
|
+
# Reactive Documentation
|
|
2
|
+
|
|
3
|
+
## Table of Contents
|
|
4
|
+
|
|
5
|
+
- [Introduction](#introduction)
|
|
6
|
+
- [Getting Started](#getting-started)
|
|
7
|
+
- [5-Minute Quick Start](#5-minute-quick-start)
|
|
8
|
+
- [Core API](#core-api)
|
|
9
|
+
- [Effect System](#effect-system)
|
|
10
|
+
- [Atomic Operations](#atomic-operations)
|
|
11
|
+
- [Advanced Effects](#advanced-effects)
|
|
12
|
+
- [Evolution Tracking](#evolution-tracking)
|
|
13
|
+
- [Prototype Chains and Pure Objects](#prototype-chains-and-pure-objects)
|
|
14
|
+
- [Recursive Touching](#recursive-touching)
|
|
15
|
+
- [Why Not Deep Watching?](#why-not-deep-watching)
|
|
16
|
+
- [Collections](#collections)
|
|
17
|
+
- [Register](#register)
|
|
18
|
+
- [Class Reactivity](#class-reactivity)
|
|
19
|
+
- [Non-Reactive System](#non-reactive-system)
|
|
20
|
+
- [Array Mapping](#array-mapping)
|
|
21
|
+
- [Projection](#projection)
|
|
22
|
+
- [Record Organization](#record-organization)
|
|
23
|
+
- [Memoization](#memoization)
|
|
24
|
+
- [Debugging and Development](#debugging-and-development)
|
|
25
|
+
- [Cycle Detection](#cycle-detection)
|
|
26
|
+
|
|
27
|
+
## Introduction
|
|
28
|
+
|
|
29
|
+
### What is Reactivity?
|
|
30
|
+
|
|
31
|
+
Reactivity is a programming paradigm where the system automatically tracks dependencies between data and automatically updates when data changes. This library provides a powerful, lightweight reactive system for JavaScript/TypeScript applications.
|
|
32
|
+
|
|
33
|
+
### Core Concepts
|
|
34
|
+
|
|
35
|
+
- **Reactive Objects**: Plain JavaScript objects wrapped with reactive capabilities
|
|
36
|
+
- **Effects**: Functions that automatically re-run when their dependencies change
|
|
37
|
+
- **Dependencies**: Reactive properties that an effect depends on
|
|
38
|
+
- **Atomic Operations**: Batching multiple state changes to execute effects only once
|
|
39
|
+
- **Evolution Tracking**: Built-in change history for reactive objects
|
|
40
|
+
- **Collections**: Reactive wrappers for Array, Map, Set, WeakMap, and WeakSet
|
|
41
|
+
|
|
42
|
+
### Basic Example
|
|
43
|
+
|
|
44
|
+
```typescript
|
|
45
|
+
import { reactive, effect } from 'mutts/reactive'
|
|
46
|
+
|
|
47
|
+
// Create a reactive object
|
|
48
|
+
const user = reactive({ name: "John", age: 30 })
|
|
49
|
+
|
|
50
|
+
// Create an effect that depends on user properties
|
|
51
|
+
effect(() => {
|
|
52
|
+
console.log(`User: ${user.name}, Age: ${user.age}`)
|
|
53
|
+
})
|
|
54
|
+
|
|
55
|
+
// When properties change, the effect automatically re-runs
|
|
56
|
+
user.name = "Jane" // Triggers effect
|
|
57
|
+
user.age = 25 // Triggers effect
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
## Getting Started
|
|
61
|
+
|
|
62
|
+
### 5-Minute Quick Start
|
|
63
|
+
|
|
64
|
+
Learn the essentials in 5 minutes:
|
|
65
|
+
|
|
66
|
+
**1. Make state reactive:**
|
|
67
|
+
```typescript
|
|
68
|
+
import { reactive, effect } from 'mutts/reactive'
|
|
69
|
+
|
|
70
|
+
const state = reactive({ count: 0, name: "John" })
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
**2. React to changes:**
|
|
74
|
+
```typescript
|
|
75
|
+
// Effects automatically re-run when dependencies change
|
|
76
|
+
effect(() => {
|
|
77
|
+
console.log(`Hello ${state.name}, count is ${state.count}`)
|
|
78
|
+
})
|
|
79
|
+
|
|
80
|
+
state.count++ // Triggers effect
|
|
81
|
+
state.name = "Jane" // Triggers effect
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
**3. Work with arrays:**
|
|
85
|
+
```typescript
|
|
86
|
+
const items = reactive([1, 2, 3])
|
|
87
|
+
|
|
88
|
+
effect(() => {
|
|
89
|
+
console.log(`Array length: ${items.length}`)
|
|
90
|
+
})
|
|
91
|
+
|
|
92
|
+
items.push(4) // Triggers effect
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
**4. Use memoization:**
|
|
96
|
+
```typescript
|
|
97
|
+
const memoized = memoize((user: User) => {
|
|
98
|
+
return expensiveComputation(user)
|
|
99
|
+
})
|
|
100
|
+
|
|
101
|
+
// Only recomputes when user changes
|
|
102
|
+
const result = memoized(user)
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
**5. Map over arrays:**
|
|
106
|
+
```typescript
|
|
107
|
+
const source = reactive([1, 2, 3])
|
|
108
|
+
const doubled = mapped(source, x => x * 2)
|
|
109
|
+
// [2, 4, 6]
|
|
110
|
+
|
|
111
|
+
source.push(4) // doubled automatically becomes [2, 4, 6, 8]
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
**Ready to go!** Continue reading for advanced features.
|
|
115
|
+
|
|
116
|
+
---
|
|
117
|
+
|
|
118
|
+
### Installation
|
|
119
|
+
|
|
120
|
+
```bash
|
|
121
|
+
npm install mutts
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
### Basic Usage
|
|
125
|
+
|
|
126
|
+
```typescript
|
|
127
|
+
import { reactive, effect } from 'mutts/reactive'
|
|
128
|
+
|
|
129
|
+
// Make an object reactive
|
|
130
|
+
const state = reactive({
|
|
131
|
+
count: 0,
|
|
132
|
+
message: "Hello"
|
|
133
|
+
})
|
|
134
|
+
|
|
135
|
+
// Create reactive effects
|
|
136
|
+
effect(() => {
|
|
137
|
+
console.log(`Count: ${state.count}`)
|
|
138
|
+
})
|
|
139
|
+
|
|
140
|
+
effect(() => {
|
|
141
|
+
console.log(`Message: ${state.message}`)
|
|
142
|
+
})
|
|
143
|
+
|
|
144
|
+
// Changes trigger effects automatically
|
|
145
|
+
state.count++ // Triggers first effect
|
|
146
|
+
state.message = "Hi" // Triggers second effect
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
### Hello World Example
|
|
150
|
+
|
|
151
|
+
```typescript
|
|
152
|
+
import { reactive, effect } from 'mutts/reactive'
|
|
153
|
+
|
|
154
|
+
// Simple counter
|
|
155
|
+
const counter = reactive({ value: 0 })
|
|
156
|
+
|
|
157
|
+
effect(() => {
|
|
158
|
+
document.body.innerHTML = `Count: ${counter.value}`
|
|
159
|
+
})
|
|
160
|
+
|
|
161
|
+
// Button click handler
|
|
162
|
+
document.getElementById('increment').onclick = () => {
|
|
163
|
+
counter.value++
|
|
164
|
+
}
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
## Core API
|
|
168
|
+
|
|
169
|
+
### `reactive()`
|
|
170
|
+
|
|
171
|
+
Makes an object reactive by wrapping it in a proxy.
|
|
172
|
+
|
|
173
|
+
```typescript
|
|
174
|
+
function reactive<T extends Record<PropertyKey, any>>(target: T): T
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
**Parameters:**
|
|
178
|
+
- `target`: The object to make reactive
|
|
179
|
+
|
|
180
|
+
**Returns:** A reactive proxy of the original object
|
|
181
|
+
|
|
182
|
+
**Example:**
|
|
183
|
+
```typescript
|
|
184
|
+
const obj = { count: 0 }
|
|
185
|
+
const reactiveObj = reactive(obj)
|
|
186
|
+
|
|
187
|
+
// reactiveObj is now reactive
|
|
188
|
+
effect(() => {
|
|
189
|
+
console.log(reactiveObj.count) // Tracks dependency
|
|
190
|
+
})
|
|
191
|
+
|
|
192
|
+
reactiveObj.count = 5 // Triggers effect
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
**Note:** The same object will always return the same proxy instance.
|
|
196
|
+
|
|
197
|
+
**Pure Objects and Prototypes:**
|
|
198
|
+
|
|
199
|
+
`reactive()` works with any object type, including:
|
|
200
|
+
- Normal objects: `reactive({ x: 1 })`
|
|
201
|
+
- Pure objects: `reactive(Object.create(null))`
|
|
202
|
+
- Objects with prototypes: `reactive(Object.create(parent))`
|
|
203
|
+
- Class instances: `reactive(new MyClass())`
|
|
204
|
+
|
|
205
|
+
See [Prototype Chains and Pure Objects](#prototype-chains-and-pure-objects) for detailed information about prototype chain handling.
|
|
206
|
+
|
|
207
|
+
### `effect()`
|
|
208
|
+
|
|
209
|
+
Creates a reactive effect that automatically re-runs when dependencies change.
|
|
210
|
+
|
|
211
|
+
```typescript
|
|
212
|
+
interface DependencyAccess {
|
|
213
|
+
tracked: DependencyFunction // Track dependencies in the current effect
|
|
214
|
+
ascend: DependencyFunction // Track dependencies in the parent effect
|
|
215
|
+
reaction: boolean // true after the first run
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
function effect(
|
|
219
|
+
fn: (access: DependencyAccess, ...args: any[]) => (ScopedCallback | undefined | void),
|
|
220
|
+
...args: any[]
|
|
221
|
+
): ScopedCallback
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
**Parameters:**
|
|
225
|
+
- `fn`: The effect function that receives dependency access and may return a cleanup function
|
|
226
|
+
- `...args`: Additional arguments that are forwarded to the effect function
|
|
227
|
+
|
|
228
|
+
**DependencyAccess:**
|
|
229
|
+
- `tracked`: Function to track dependencies in the current effect (use this by default)
|
|
230
|
+
- `ascend`: Function to track dependencies in the parent effect (useful for effects that should be cleaned up with their parent)
|
|
231
|
+
- `reaction`: Boolean flag that is `false` during the initial execution and `true` for every subsequent re-run triggered by dependency changes
|
|
232
|
+
|
|
233
|
+
**Returns:** A cleanup function to stop the effect
|
|
234
|
+
|
|
235
|
+
**Example:**
|
|
236
|
+
```typescript
|
|
237
|
+
const state = reactive({ count: 0, mood: 'happy' })
|
|
238
|
+
|
|
239
|
+
const cleanup = effect(() => {
|
|
240
|
+
console.log(`Count is: ${state.count}`)
|
|
241
|
+
// Optional cleanup called before next run
|
|
242
|
+
return () => console.log('Cleaning up...')
|
|
243
|
+
})
|
|
244
|
+
|
|
245
|
+
state.count++ // Does trigger: 1- the cleaning, 2- the effect
|
|
246
|
+
state.mood = 'surprised' // Does not trigger the effect
|
|
247
|
+
|
|
248
|
+
// Later...
|
|
249
|
+
cleanup() // Stops the effect
|
|
250
|
+
```
|
|
251
|
+
|
|
252
|
+
You can also branch on the `reaction` flag to separate initialisation logic from update logic:
|
|
253
|
+
|
|
254
|
+
```typescript
|
|
255
|
+
effect(({ reaction }) => {
|
|
256
|
+
if (!reaction) {
|
|
257
|
+
console.log('Initial render setup')
|
|
258
|
+
} else {
|
|
259
|
+
console.log('Reacting to dependency change')
|
|
260
|
+
}
|
|
261
|
+
})
|
|
262
|
+
```
|
|
263
|
+
|
|
264
|
+
**Using effect with arguments (useful in loops):**
|
|
265
|
+
```typescript
|
|
266
|
+
const items = reactive([{ id: 1 }, { id: 2 }, { id: 3 }])
|
|
267
|
+
|
|
268
|
+
// Create effects in a loop, passing loop variables
|
|
269
|
+
for (let i = 0; i < items.length; i++) {
|
|
270
|
+
effect((access, index) => {
|
|
271
|
+
console.log(`Item ${index}:`, items[index])
|
|
272
|
+
}, i) // Pass the loop variable as argument
|
|
273
|
+
}
|
|
274
|
+
```
|
|
275
|
+
|
|
276
|
+
### `unwrap()`
|
|
277
|
+
|
|
278
|
+
Gets the original, non-reactive object from a reactive proxy.
|
|
279
|
+
|
|
280
|
+
```typescript
|
|
281
|
+
function unwrap<T>(proxy: T): T
|
|
282
|
+
```
|
|
283
|
+
|
|
284
|
+
**Example:**
|
|
285
|
+
```typescript
|
|
286
|
+
const original = { count: 0 }
|
|
287
|
+
const reactive = reactive(original)
|
|
288
|
+
const unwrapped = unwrap(reactive)
|
|
289
|
+
|
|
290
|
+
console.log(unwrapped === original) // true
|
|
291
|
+
console.log(unwrapped === reactive) // false
|
|
292
|
+
```
|
|
293
|
+
|
|
294
|
+
### `isReactive()`
|
|
295
|
+
|
|
296
|
+
Checks if an object is a reactive proxy.
|
|
297
|
+
|
|
298
|
+
```typescript
|
|
299
|
+
function isReactive(obj: any): boolean
|
|
300
|
+
```
|
|
301
|
+
|
|
302
|
+
### `isNonReactive()`
|
|
303
|
+
|
|
304
|
+
Checks if an object is marked as non-reactive.
|
|
305
|
+
|
|
306
|
+
```typescript
|
|
307
|
+
function isNonReactive(obj: any): boolean
|
|
308
|
+
```
|
|
309
|
+
|
|
310
|
+
## Effect System
|
|
311
|
+
|
|
312
|
+
### Basic Effects
|
|
313
|
+
|
|
314
|
+
Effects are the core of the reactive system. They automatically track dependencies and re-run when those dependencies change.
|
|
315
|
+
|
|
316
|
+
```typescript
|
|
317
|
+
const state = reactive({ count: 0, name: "John" })
|
|
318
|
+
|
|
319
|
+
effect(() => {
|
|
320
|
+
// This effect depends on state.count
|
|
321
|
+
console.log(`Count: ${state.count}`)
|
|
322
|
+
})
|
|
323
|
+
|
|
324
|
+
// Only changing count triggers the effect
|
|
325
|
+
state.count = 5 // Triggers effect
|
|
326
|
+
state.name = "Jane" // Does NOT trigger effect
|
|
327
|
+
```
|
|
328
|
+
|
|
329
|
+
### Effect Cleanup
|
|
330
|
+
|
|
331
|
+
Effects return cleanup functions that you can call to stop tracking dependencies.
|
|
332
|
+
|
|
333
|
+
```typescript
|
|
334
|
+
const state = reactive({ count: 0 })
|
|
335
|
+
|
|
336
|
+
const stopEffect = effect(() => {
|
|
337
|
+
console.log(`Count: ${state.count}`)
|
|
338
|
+
})
|
|
339
|
+
|
|
340
|
+
// Later...
|
|
341
|
+
stopEffect() // Stops the effect
|
|
342
|
+
|
|
343
|
+
state.count = 10 // No longer triggers the effect
|
|
344
|
+
```
|
|
345
|
+
|
|
346
|
+
### Automatic Effect Cleanup
|
|
347
|
+
|
|
348
|
+
The reactive system provides **automatic cleanup** for effects, making memory management much easier. You **do not** have to call the cleanup function in most cases, but you **may** want to, especially if your effects have side effects like timers, DOM manipulation, or event listeners.
|
|
349
|
+
|
|
350
|
+
#### How Automatic Cleanup Works
|
|
351
|
+
|
|
352
|
+
1. **Parent-Child Cleanup**: When an effect is created inside another effect, it becomes a "child" of the parent effect. When the parent effect is cleaned up, it also cleans up its child effects.
|
|
353
|
+
|
|
354
|
+
2. **Garbage Collection Cleanup**: For top-level effects (not created inside other effects), the system uses JavaScript's garbage collection to automatically clean them up when their cleanup function no longer referenced.
|
|
355
|
+
|
|
356
|
+
#### Examples
|
|
357
|
+
|
|
358
|
+
**Parent-Child Cleanup:**
|
|
359
|
+
```typescript
|
|
360
|
+
const state = reactive({ a: 1, b: 2 })
|
|
361
|
+
|
|
362
|
+
const stopParent = effect(() => {
|
|
363
|
+
state.a
|
|
364
|
+
|
|
365
|
+
// Child effect - tied to the parent lifecycle
|
|
366
|
+
effect(() => {
|
|
367
|
+
state.b
|
|
368
|
+
return () => console.log('Child cleanup')
|
|
369
|
+
})
|
|
370
|
+
|
|
371
|
+
return () => console.log('Parent cleanup')
|
|
372
|
+
})
|
|
373
|
+
|
|
374
|
+
// Cleans up both parent and child
|
|
375
|
+
stopParent() // Logs (order may vary): "Child cleanup", then "Parent cleanup"
|
|
376
|
+
```
|
|
377
|
+
|
|
378
|
+
**Garbage Collection Cleanup:**
|
|
379
|
+
```typescript
|
|
380
|
+
const state = reactive({ value: 1 })
|
|
381
|
+
|
|
382
|
+
// Top-level effect - automatically cleaned up via garbage collection
|
|
383
|
+
effect(() => {
|
|
384
|
+
state.value
|
|
385
|
+
return () => console.log('GC cleanup')
|
|
386
|
+
})
|
|
387
|
+
|
|
388
|
+
// No explicit cleanup needed - will be cleaned up when garbage collected
|
|
389
|
+
```
|
|
390
|
+
|
|
391
|
+
**When You Should Store Cleanup Functions:**
|
|
392
|
+
|
|
393
|
+
While cleanup can be automatic via GC, you should **store and remember** cleanup functions both to prevent the effect from being garbage-collected (keeping it alive) and to perform immediate cleanup when needed. If you don't hold a reference to the cleanup (or to the effect), the effect can be collected and its cleanup called automatically by GC; storing a reference keeps it active under your control:
|
|
394
|
+
|
|
395
|
+
```typescript
|
|
396
|
+
const state = reactive({ value: 1 })
|
|
397
|
+
const activeEffects: (() => void)[] = []
|
|
398
|
+
|
|
399
|
+
// Store cleanup functions for effects with side effects
|
|
400
|
+
for (let i = 0; i < 3; i++) {
|
|
401
|
+
const stopEffect = effect(() => {
|
|
402
|
+
state.value
|
|
403
|
+
|
|
404
|
+
// Side effect that needs immediate cleanup
|
|
405
|
+
const intervalId = setInterval(() => {
|
|
406
|
+
console.log(`Timer ${i} running`)
|
|
407
|
+
}, 1000)
|
|
408
|
+
|
|
409
|
+
return () => {
|
|
410
|
+
clearInterval(intervalId) // Prevent memory leaks
|
|
411
|
+
}
|
|
412
|
+
})
|
|
413
|
+
|
|
414
|
+
activeEffects.push(stopEffect)
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
// Clean up all effects when needed
|
|
418
|
+
activeEffects.forEach(stop => stop())
|
|
419
|
+
```
|
|
420
|
+
|
|
421
|
+
**Key Points:**
|
|
422
|
+
|
|
423
|
+
- **You do not have to call cleanup** - GC may clean up effects when no references remain
|
|
424
|
+
- **You may want to call cleanup** - especially for effects with side effects
|
|
425
|
+
- **Store cleanup references to keep effects alive** - holding a reference prevents GC cleanup and gives you explicit stop control
|
|
426
|
+
- **Parent cleanup cleans child effects** - stopping a parent also stops its child effects
|
|
427
|
+
- **Child effects are referenced by parent effects** - and therefore are not subject to GC cleanups
|
|
428
|
+
|
|
429
|
+
### Effect Dependencies
|
|
430
|
+
|
|
431
|
+
Effects automatically track which reactive properties they access.
|
|
432
|
+
|
|
433
|
+
```typescript
|
|
434
|
+
const state = reactive({ a: 1, b: 2, c: 3 })
|
|
435
|
+
|
|
436
|
+
effect(() => {
|
|
437
|
+
// Only tracks state.a and state.b
|
|
438
|
+
console.log(`Sum: ${state.a + state.b}`)
|
|
439
|
+
})
|
|
440
|
+
|
|
441
|
+
// Only these changes trigger the effect
|
|
442
|
+
state.a = 5 // Triggers effect
|
|
443
|
+
state.b = 10 // Triggers effect
|
|
444
|
+
state.c = 15 // Does NOT trigger effect
|
|
445
|
+
```
|
|
446
|
+
|
|
447
|
+
### Async Effects and the `access` Parameter
|
|
448
|
+
|
|
449
|
+
The `effect` function provides a special `access` parameter with `tracked` and `ascend` functions that restore the active effect context for dependency tracking in asynchronous operations. This is crucial because async functions lose the global active effect context when they yield control.
|
|
450
|
+
|
|
451
|
+
#### The Problem with Async Effects
|
|
452
|
+
|
|
453
|
+
When an effect function is synchronous, reactive property access automatically tracks dependencies, however, in async functions, the active effect context is lost when the function yields control.
|
|
454
|
+
|
|
455
|
+
The `access.tracked()` function restores the active effect context for dependency tracking.
|
|
456
|
+
|
|
457
|
+
#### Understanding the active effect context
|
|
458
|
+
|
|
459
|
+
The reactive system uses a global active effect variable to track which effect is currently running:
|
|
460
|
+
|
|
461
|
+
```typescript
|
|
462
|
+
// Synchronous effect - active effect is maintained throughout
|
|
463
|
+
effect(() => {
|
|
464
|
+
// active effect = this effect
|
|
465
|
+
const value = state.count // ✅ Tracked (active effect is set)
|
|
466
|
+
// active effect = this effect (still set)
|
|
467
|
+
const another = state.name // ✅ Tracked (active effect is still set)
|
|
468
|
+
})
|
|
469
|
+
|
|
470
|
+
// Async effect - active effect is lost after await
|
|
471
|
+
effect(async () => {
|
|
472
|
+
// active effect = this effect
|
|
473
|
+
const value = state.count // ✅ Tracked (active effect is set)
|
|
474
|
+
|
|
475
|
+
await someAsyncOperation() // Function exits, active effect = undefined
|
|
476
|
+
|
|
477
|
+
// active effect = undefined (lost!)
|
|
478
|
+
const another = state.name // ❌ NOT tracked (no active effect)
|
|
479
|
+
})
|
|
480
|
+
|
|
481
|
+
// Async effect with access.tracked() - active effect is restored
|
|
482
|
+
effect(async ({ tracked }) => {
|
|
483
|
+
// active effect = this effect
|
|
484
|
+
const value = state.count // ✅ Tracked (active effect is set)
|
|
485
|
+
|
|
486
|
+
await someAsyncOperation() // Function exits, active effect = undefined
|
|
487
|
+
|
|
488
|
+
// tracked() temporarily restores active effect for the callback
|
|
489
|
+
const another = tracked(() => state.name) // ✅ Tracked (active effect restored)
|
|
490
|
+
})
|
|
491
|
+
```
|
|
492
|
+
|
|
493
|
+
#### Key Benefits of `access.tracked()` in Async Effects
|
|
494
|
+
|
|
495
|
+
1. **Restored Context**: `access.tracked()` temporarily restores the active effect context for dependency tracking
|
|
496
|
+
2. **Consistent Tracking**: Reactive property access works the same way before and after `await`
|
|
497
|
+
|
|
498
|
+
### Using `ascend` for Parent Effect Tracking
|
|
499
|
+
|
|
500
|
+
The `ascend` function allows you to track dependencies in the parent effect instead of the current effect. This is useful when you want child effects to be cleaned up with their parent, but their dependencies should trigger the parent effect.
|
|
501
|
+
|
|
502
|
+
```typescript
|
|
503
|
+
const inputs = reactive([1, 2, 3])
|
|
504
|
+
|
|
505
|
+
effect(({ ascend }) => {
|
|
506
|
+
// Track inputs.length in the parent effect
|
|
507
|
+
const length = inputs.length
|
|
508
|
+
|
|
509
|
+
if (length > 0) {
|
|
510
|
+
// Use ascend to create effects that track dependencies in the parent
|
|
511
|
+
// When parent is cleaned up, these child effects are also cleaned up
|
|
512
|
+
ascend(() => {
|
|
513
|
+
// Dependencies here are tracked in the parent effect
|
|
514
|
+
inputs.forEach((item, index) => {
|
|
515
|
+
console.log(`Item ${index}:`, item)
|
|
516
|
+
})
|
|
517
|
+
})
|
|
518
|
+
}
|
|
519
|
+
})
|
|
520
|
+
```
|
|
521
|
+
<|tool▁calls▁begin|><|tool▁call▁begin|>
|
|
522
|
+
grep
|
|
523
|
+
|
|
524
|
+
**When to use `ascend`:**
|
|
525
|
+
- When creating child effects that should be cleaned up with their parent
|
|
526
|
+
- When child effect dependencies should trigger the parent effect
|
|
527
|
+
- For managing effect hierarchies where child effects depend on parent context
|
|
528
|
+
|
|
529
|
+
### Nested Effects
|
|
530
|
+
|
|
531
|
+
Effects can be created inside other effects and will have separate effect scopes:
|
|
532
|
+
|
|
533
|
+
```typescript
|
|
534
|
+
import { effect, reactive } from 'mutts/reactive'
|
|
535
|
+
|
|
536
|
+
const state = reactive({ a: 0, b: 0 })
|
|
537
|
+
|
|
538
|
+
const stopOuter = effect(() => {
|
|
539
|
+
state.a
|
|
540
|
+
|
|
541
|
+
// Create an inner effect with its own scope
|
|
542
|
+
const stopInner = effect(() => {
|
|
543
|
+
state.b
|
|
544
|
+
})
|
|
545
|
+
|
|
546
|
+
// Return cleanup function for the inner effect
|
|
547
|
+
return stopInner
|
|
548
|
+
})
|
|
549
|
+
```
|
|
550
|
+
|
|
551
|
+
### `untracked()`
|
|
552
|
+
|
|
553
|
+
The `untracked()` function allows you to run code without tracking dependencies, which can be useful for creating effects or performing operations that shouldn't be part of the current effect's dependency graph.
|
|
554
|
+
|
|
555
|
+
```typescript
|
|
556
|
+
import { effect, untracked, reactive } from 'mutts/reactive'
|
|
557
|
+
|
|
558
|
+
const state = reactive({ a: 0, b: 0 })
|
|
559
|
+
|
|
560
|
+
effect(() => {
|
|
561
|
+
state.a
|
|
562
|
+
|
|
563
|
+
// Create an inner effect without tracking the creation under the outer effect
|
|
564
|
+
let stopInner: (() => void) | undefined
|
|
565
|
+
untracked(() => {
|
|
566
|
+
stopInner = effect(() => {
|
|
567
|
+
state.b
|
|
568
|
+
})
|
|
569
|
+
})
|
|
570
|
+
|
|
571
|
+
// Optionally stop it immediately to avoid accumulating watchers
|
|
572
|
+
stopInner && stopInner()
|
|
573
|
+
})
|
|
574
|
+
```
|
|
575
|
+
|
|
576
|
+
**Use cases for `untracked()`:**
|
|
577
|
+
- Creating effects inside other effects without coupling their dependencies
|
|
578
|
+
- Performing side effects that shouldn't trigger when dependencies change
|
|
579
|
+
- Avoiding circular dependencies in complex reactive systems
|
|
580
|
+
|
|
581
|
+
### Effect Options and debugging
|
|
582
|
+
|
|
583
|
+
Configure the reactive system behavior:
|
|
584
|
+
|
|
585
|
+
```typescript
|
|
586
|
+
import { options as reactiveOptions } from 'mutts/reactive'
|
|
587
|
+
|
|
588
|
+
// Set maximum effect chain depth
|
|
589
|
+
reactiveOptions.maxEffectChain = 50
|
|
590
|
+
|
|
591
|
+
// Set maximum deep watch traversal depth
|
|
592
|
+
reactiveOptions.maxDeepWatchDepth = 200
|
|
593
|
+
|
|
594
|
+
// Enable/disable recursive touching (default: true)
|
|
595
|
+
reactiveOptions.recursiveTouching = false
|
|
596
|
+
|
|
597
|
+
// Enable debug logging
|
|
598
|
+
reactiveOptions.enter = (effect) => console.log('Entering effect:', effect)
|
|
599
|
+
reactiveOptions.leave = (effect) => console.log('Leaving effect:', effect)
|
|
600
|
+
reactiveOptions.chain = (caller, target) => console.log('Chaining:', caller, '->', target)
|
|
601
|
+
```
|
|
602
|
+
|
|
603
|
+
|
|
604
|
+
### `@reactive` Decorator
|
|
605
|
+
|
|
606
|
+
The `@reactive` decorator makes class instances automatically reactive. This is the recommended approach for adding reactivity to classes.
|
|
607
|
+
|
|
608
|
+
```typescript
|
|
609
|
+
import { reactive } from 'mutts/reactive'
|
|
610
|
+
|
|
611
|
+
@reactive
|
|
612
|
+
class User {
|
|
613
|
+
name: string
|
|
614
|
+
age: number
|
|
615
|
+
|
|
616
|
+
constructor(name: string, age: number) {
|
|
617
|
+
this.name = name
|
|
618
|
+
this.age = age
|
|
619
|
+
}
|
|
620
|
+
|
|
621
|
+
updateAge(newAge: number) {
|
|
622
|
+
this.age = newAge
|
|
623
|
+
}
|
|
624
|
+
}
|
|
625
|
+
|
|
626
|
+
const user = new User("John", 30)
|
|
627
|
+
|
|
628
|
+
effect(() => {
|
|
629
|
+
console.log(`User: ${user.name}, Age: ${user.age}`)
|
|
630
|
+
})
|
|
631
|
+
|
|
632
|
+
user.updateAge(31) // Triggers effect
|
|
633
|
+
user.name = "Jane" // Triggers effect
|
|
634
|
+
```
|
|
635
|
+
|
|
636
|
+
### Functional Syntax
|
|
637
|
+
|
|
638
|
+
You can also use the functional syntax for making classes reactive:
|
|
639
|
+
|
|
640
|
+
```typescript
|
|
641
|
+
import { reactive } from 'mutts/reactive'
|
|
642
|
+
|
|
643
|
+
class User {
|
|
644
|
+
name: string
|
|
645
|
+
age: number
|
|
646
|
+
|
|
647
|
+
constructor(name: string, age: number) {
|
|
648
|
+
this.name = name
|
|
649
|
+
this.age = age
|
|
650
|
+
}
|
|
651
|
+
|
|
652
|
+
updateAge(newAge: number) {
|
|
653
|
+
this.age = newAge
|
|
654
|
+
}
|
|
655
|
+
}
|
|
656
|
+
|
|
657
|
+
const ReactiveUser = reactive(User)
|
|
658
|
+
const user = new ReactiveUser("John", 30)
|
|
659
|
+
|
|
660
|
+
effect(() => {
|
|
661
|
+
console.log(`User: ${user.name}, Age: ${user.age}`)
|
|
662
|
+
})
|
|
663
|
+
|
|
664
|
+
user.updateAge(31) // Triggers effect
|
|
665
|
+
user.name = "Jane" // Triggers effect
|
|
666
|
+
```
|
|
667
|
+
|
|
668
|
+
### `ReactiveBase` for Complex Inheritance
|
|
669
|
+
|
|
670
|
+
For complex inheritance trees, especially when you need to solve constructor reactivity issues, extend `ReactiveBase`:
|
|
671
|
+
|
|
672
|
+
```typescript
|
|
673
|
+
import { ReactiveBase, reactive } from 'mutts/reactive'
|
|
674
|
+
|
|
675
|
+
class GameObject extends ReactiveBase {
|
|
676
|
+
id = 'game-object'
|
|
677
|
+
position = { x: 0, y: 0 }
|
|
678
|
+
}
|
|
679
|
+
|
|
680
|
+
class Entity extends GameObject {
|
|
681
|
+
health = 100
|
|
682
|
+
}
|
|
683
|
+
|
|
684
|
+
@reactive
|
|
685
|
+
class Player extends Entity {
|
|
686
|
+
name = 'Player'
|
|
687
|
+
level = 1
|
|
688
|
+
}
|
|
689
|
+
|
|
690
|
+
const player = new Player()
|
|
691
|
+
|
|
692
|
+
effect(() => {
|
|
693
|
+
console.log(`Player ${player.name} at (${player.position.x}, ${player.position.y})`)
|
|
694
|
+
})
|
|
695
|
+
|
|
696
|
+
player.position.x = 10 // Triggers effect
|
|
697
|
+
player.health = 80 // Triggers effect
|
|
698
|
+
```
|
|
699
|
+
|
|
700
|
+
**Advantages of `ReactiveBase`:**
|
|
701
|
+
|
|
702
|
+
1. **Constructor Reactivity**: Solves the issue where `this` in the constructor is not yet reactive
|
|
703
|
+
2. **Inheritance Safety**: Prevents reactivity from being added to prototype chains in complex inheritance trees
|
|
704
|
+
3. **No Side Effects**: The base class itself has no effect - it only enables proper reactivity when combined with `@reactive`
|
|
705
|
+
|
|
706
|
+
### Choosing the Right Approach
|
|
707
|
+
|
|
708
|
+
**Use `@reactive` decorator when:**
|
|
709
|
+
- You have simple classes without complex inheritance
|
|
710
|
+
- You want the cleanest, most modern syntax
|
|
711
|
+
- You don't need to modify or use `this` in the constructor
|
|
712
|
+
|
|
713
|
+
**Use `ReactiveBase` + `@reactive` when:**
|
|
714
|
+
- You have complex inheritance trees (like game objects, UI components)
|
|
715
|
+
- You need to modify or use `this` in the constructor
|
|
716
|
+
- You want to prevent reactivity from being added to prototype chains
|
|
717
|
+
|
|
718
|
+
### Making Existing Class Instances Reactive
|
|
719
|
+
|
|
720
|
+
You can also make existing class instances reactive:
|
|
721
|
+
|
|
722
|
+
```typescript
|
|
723
|
+
class Counter {
|
|
724
|
+
count = 0
|
|
725
|
+
|
|
726
|
+
increment() {
|
|
727
|
+
this.count++
|
|
728
|
+
}
|
|
729
|
+
}
|
|
730
|
+
|
|
731
|
+
const counter = new Counter()
|
|
732
|
+
const reactiveCounter = reactive(counter)
|
|
733
|
+
|
|
734
|
+
effect(() => {
|
|
735
|
+
console.log('Count:', reactiveCounter.count)
|
|
736
|
+
})
|
|
737
|
+
|
|
738
|
+
reactiveCounter.increment() // Triggers effect
|
|
739
|
+
```
|
|
740
|
+
|
|
741
|
+
### Method Reactivity
|
|
742
|
+
|
|
743
|
+
Methods that modify properties automatically trigger effects:
|
|
744
|
+
|
|
745
|
+
```typescript
|
|
746
|
+
@reactive
|
|
747
|
+
class ShoppingCart {
|
|
748
|
+
items: string[] = []
|
|
749
|
+
|
|
750
|
+
addItem(item: string) {
|
|
751
|
+
this.items.push(item)
|
|
752
|
+
}
|
|
753
|
+
|
|
754
|
+
removeItem(item: string) {
|
|
755
|
+
const index = this.items.indexOf(item)
|
|
756
|
+
if (index > -1) {
|
|
757
|
+
this.items.splice(index, 1)
|
|
758
|
+
}
|
|
759
|
+
}
|
|
760
|
+
}
|
|
761
|
+
|
|
762
|
+
const cart = new ShoppingCart()
|
|
763
|
+
|
|
764
|
+
effect(() => {
|
|
765
|
+
console.log('Cart items:', cart.items)
|
|
766
|
+
})
|
|
767
|
+
|
|
768
|
+
cart.addItem('Apple') // Triggers effect
|
|
769
|
+
cart.removeItem('Apple') // Triggers effect
|
|
770
|
+
```
|
|
771
|
+
|
|
772
|
+
### Inheritance Support
|
|
773
|
+
|
|
774
|
+
The `@reactive` decorator works with inheritance:
|
|
775
|
+
|
|
776
|
+
```typescript
|
|
777
|
+
@reactive
|
|
778
|
+
class Animal {
|
|
779
|
+
species: string
|
|
780
|
+
|
|
781
|
+
constructor(species: string) {
|
|
782
|
+
this.species = species
|
|
783
|
+
}
|
|
784
|
+
}
|
|
785
|
+
|
|
786
|
+
class Dog extends Animal {
|
|
787
|
+
breed: string
|
|
788
|
+
|
|
789
|
+
constructor(breed: string) {
|
|
790
|
+
super('Canis')
|
|
791
|
+
this.breed = breed
|
|
792
|
+
}
|
|
793
|
+
}
|
|
794
|
+
|
|
795
|
+
const dog = new Dog('Golden Retriever')
|
|
796
|
+
|
|
797
|
+
effect(() => {
|
|
798
|
+
console.log(`${dog.species}: ${dog.breed}`)
|
|
799
|
+
})
|
|
800
|
+
|
|
801
|
+
dog.breed = 'Labrador' // Triggers effect
|
|
802
|
+
```
|
|
803
|
+
|
|
804
|
+
**Note**: When using inheritance with the `@reactive` decorator, apply it to the base class. The decorator will automatically handle inheritance properly.
|
|
805
|
+
|
|
806
|
+
## Non-Reactive System
|
|
807
|
+
|
|
808
|
+
### `unreactive()`
|
|
809
|
+
|
|
810
|
+
Marks objects or classes as non-reactive, preventing them from being wrapped.
|
|
811
|
+
|
|
812
|
+
```typescript
|
|
813
|
+
function unreactive<T>(target: T): T
|
|
814
|
+
function unreactive(target: Constructor<T>): Constructor<T>
|
|
815
|
+
```
|
|
816
|
+
|
|
817
|
+
**Examples:**
|
|
818
|
+
|
|
819
|
+
```typescript
|
|
820
|
+
// Mark individual object as non-reactive
|
|
821
|
+
const obj = { count: 0 }
|
|
822
|
+
unreactive(obj)
|
|
823
|
+
const reactiveObj = reactive(obj) // Returns obj unchanged
|
|
824
|
+
|
|
825
|
+
// Mark entire class as non-reactive
|
|
826
|
+
class Utility {
|
|
827
|
+
static helper() { return 'help' }
|
|
828
|
+
}
|
|
829
|
+
unreactive(Utility)
|
|
830
|
+
const instance = new Utility()
|
|
831
|
+
const reactiveInstance = reactive(instance) // Returns instance unchanged
|
|
832
|
+
```
|
|
833
|
+
|
|
834
|
+
### `@unreactive` Decorator
|
|
835
|
+
|
|
836
|
+
Mark class properties as non-reactive using class-level syntax.
|
|
837
|
+
|
|
838
|
+
```typescript
|
|
839
|
+
@reactive
|
|
840
|
+
@unreactive('id')
|
|
841
|
+
class User {
|
|
842
|
+
id: string = 'user-123'
|
|
843
|
+
|
|
844
|
+
name: string = 'John'
|
|
845
|
+
age: number = 30
|
|
846
|
+
}
|
|
847
|
+
|
|
848
|
+
const user = new User()
|
|
849
|
+
|
|
850
|
+
effect(() => {
|
|
851
|
+
console.log(user.name, user.age) // Tracks these
|
|
852
|
+
console.log(user.id) // Does NOT track this
|
|
853
|
+
})
|
|
854
|
+
|
|
855
|
+
user.name = 'Jane' // Triggers effect
|
|
856
|
+
user.id = 'new-id' // Does NOT trigger effect
|
|
857
|
+
```
|
|
858
|
+
|
|
859
|
+
You can mark multiple properties as unreactive:
|
|
860
|
+
|
|
861
|
+
```typescript
|
|
862
|
+
@reactive
|
|
863
|
+
@unreactive('id', 'metadata', 'internalState')
|
|
864
|
+
class User {
|
|
865
|
+
id: string = 'user-123'
|
|
866
|
+
metadata: any = {}
|
|
867
|
+
internalState: any = {}
|
|
868
|
+
|
|
869
|
+
name: string = 'John'
|
|
870
|
+
age: number = 30
|
|
871
|
+
}
|
|
872
|
+
```
|
|
873
|
+
|
|
874
|
+
### Non-Reactive Classes
|
|
875
|
+
|
|
876
|
+
Classes marked as non-reactive bypass the reactive system entirely:
|
|
877
|
+
|
|
878
|
+
```typescript
|
|
879
|
+
class Config {
|
|
880
|
+
apiUrl: string = 'https://api.example.com'
|
|
881
|
+
timeout: number = 5000
|
|
882
|
+
}
|
|
883
|
+
|
|
884
|
+
unreactive(Config)
|
|
885
|
+
```
|
|
886
|
+
|
|
887
|
+
Alternatively, you can use the `@unreactive` decorator without arguments to mark the entire class as non-reactive:
|
|
888
|
+
|
|
889
|
+
```typescript
|
|
890
|
+
@unreactive
|
|
891
|
+
class Config {
|
|
892
|
+
apiUrl: string = 'https://api.example.com'
|
|
893
|
+
timeout: number = 5000
|
|
894
|
+
}
|
|
895
|
+
|
|
896
|
+
const ReactiveConfig = reactive(Config)
|
|
897
|
+
const config = new ReactiveConfig()
|
|
898
|
+
|
|
899
|
+
effect(() => {
|
|
900
|
+
console.log('Config:', config.apiUrl, config.timeout)
|
|
901
|
+
})
|
|
902
|
+
|
|
903
|
+
// These changes won't trigger effects
|
|
904
|
+
config.apiUrl = 'https://new-api.example.com'
|
|
905
|
+
config.timeout = 10000
|
|
906
|
+
```
|
|
907
|
+
|
|
908
|
+
### Making Special Reactive Objects Non-Reactive
|
|
909
|
+
|
|
910
|
+
Special reactive objects (Arrays, Maps, Sets, WeakMaps, WeakSets) can also be made non-reactive:
|
|
911
|
+
|
|
912
|
+
```typescript
|
|
913
|
+
// Make individual reactive collections non-reactive
|
|
914
|
+
const array = reactive([1, 2, 3])
|
|
915
|
+
unreactive(array) // array is no longer reactive
|
|
916
|
+
|
|
917
|
+
const map = reactive(new Map([['key', 'value']]))
|
|
918
|
+
unreactive(map) // map is no longer reactive
|
|
919
|
+
|
|
920
|
+
const set = reactive(new Set([1, 2, 3]))
|
|
921
|
+
unreactive(set) // set is no longer reactive
|
|
922
|
+
```
|
|
923
|
+
|
|
924
|
+
**Making reactive collection classes non-reactive:**
|
|
925
|
+
|
|
926
|
+
```typescript
|
|
927
|
+
// Make the entire ReactiveArray class non-reactive
|
|
928
|
+
unreactive(ReactiveArray)
|
|
929
|
+
|
|
930
|
+
// Now all ReactiveArray instances will be non-reactive
|
|
931
|
+
const array = reactive([1, 2, 3]) // Returns non-reactive array
|
|
932
|
+
const reactiveArray = new ReactiveArray([1, 2, 3]) // Non-reactive instance
|
|
933
|
+
|
|
934
|
+
// Make other reactive collection classes non-reactive
|
|
935
|
+
unreactive(ReactiveMap)
|
|
936
|
+
unreactive(ReactiveSet)
|
|
937
|
+
unreactive(ReactiveWeakMap)
|
|
938
|
+
unreactive(ReactiveWeakSet)
|
|
939
|
+
```
|
|
940
|
+
|
|
941
|
+
**Use cases for non-reactive collections:**
|
|
942
|
+
- Large datasets that don't need reactivity
|
|
943
|
+
- Performance-critical operations
|
|
944
|
+
- Static configuration data
|
|
945
|
+
- Temporary data structures
|
|
946
|
+
|
|
947
|
+
### Performance Considerations
|
|
948
|
+
|
|
949
|
+
Non-reactive objects can improve performance:
|
|
950
|
+
|
|
951
|
+
```typescript
|
|
952
|
+
// Good: Mark large, rarely-changing objects as non-reactive
|
|
953
|
+
const config = unreactive({
|
|
954
|
+
apiEndpoints: { /* large config object */ },
|
|
955
|
+
featureFlags: { /* many flags */ }
|
|
956
|
+
})
|
|
957
|
+
|
|
958
|
+
// Good: Mark utility classes as non-reactive
|
|
959
|
+
class MathUtils {
|
|
960
|
+
static PI = 3.14159
|
|
961
|
+
static square(x: number) { return x * x }
|
|
962
|
+
}
|
|
963
|
+
unreactive(MathUtils)
|
|
964
|
+
|
|
965
|
+
// Good: Mark properties that don't need reactivity
|
|
966
|
+
class User {
|
|
967
|
+
@unreactive
|
|
968
|
+
metadata: any = {} // Large metadata object
|
|
969
|
+
|
|
970
|
+
name: string = 'John' // This should be reactive
|
|
971
|
+
}
|
|
972
|
+
```
|
|
973
|
+
|