watch-state 3.5.1 → 3.6.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/Compute/Compute.d.ts +11 -4
- package/Compute/Compute.es6.js +19 -8
- package/Compute/Compute.js +19 -8
- package/Observable/Observable.d.ts +5 -1
- package/Observable/Observable.es6.js +9 -1
- package/Observable/Observable.js +9 -1
- package/README.md +228 -179
- package/State/State.d.ts +38 -4
- package/State/State.es6.js +28 -3
- package/State/State.js +28 -3
- package/Watch/Watch.d.ts +12 -4
- package/Watch/Watch.es6.js +20 -9
- package/Watch/Watch.js +20 -9
- package/helpers/bindObserver/bindObserver.es6.js +2 -2
- package/helpers/bindObserver/bindObserver.js +2 -2
- package/helpers/clearWatcher/clearWatcher.es6.js +1 -1
- package/helpers/clearWatcher/clearWatcher.js +1 -1
- package/helpers/destroyWatchers/destroyWatchers.es6.js +1 -1
- package/helpers/destroyWatchers/destroyWatchers.js +1 -1
- package/index.min.js +1 -1
- package/package.json +4 -4
- package/types.d.ts +12 -2
- package/utils/createEvent/createEvent.es6.js +1 -0
- package/utils/createEvent/createEvent.js +1 -0
package/README.md
CHANGED
|
@@ -30,7 +30,7 @@
|
|
|
30
30
|
<br>
|
|
31
31
|
<b>Light</b>
|
|
32
32
|
<br>
|
|
33
|
-
Less than 1 KB
|
|
33
|
+
Less than 1 KB gzip
|
|
34
34
|
</span></td>
|
|
35
35
|
<td align="center"><span>
|
|
36
36
|
<a href="https://d8corp.github.io/watch-state/coverage/lcov-report" target="_blank">
|
|
@@ -53,8 +53,14 @@
|
|
|
53
53
|
<a href="https://www.npmtrends.com/watch-state" target="_blank">
|
|
54
54
|
<img src="https://img.shields.io/npm/dm/watch-state.svg" alt="watch-state downloads">
|
|
55
55
|
</a>
|
|
56
|
+
<a href="https://github.com/d8corp/watch-state/tree/master/release" target="_blank">
|
|
57
|
+
<img src="https://packagephobia.com/badge?p=watch-state" alt="watch-state install size">
|
|
58
|
+
</a>
|
|
59
|
+
<a href="https://cdn.jsdelivr.net/npm/watch-state" target="_blank">
|
|
60
|
+
<img src="https://img.badgesize.io/https:/cdn.jsdelivr.net/npm/watch-state?compression=gzip" alt="watch-state gzip size">
|
|
61
|
+
</a>
|
|
56
62
|
<a href="https://www.typescriptlang.org" target="_blank">
|
|
57
|
-
<img src="https://img.shields.io/npm/types/watch-state" alt="
|
|
63
|
+
<img src="https://img.shields.io/npm/types/watch-state" alt="TypeScript">
|
|
58
64
|
</a>
|
|
59
65
|
<a href="https://packagequality.com/#?package=watch-state" target="_blank">
|
|
60
66
|
<img src="https://packagequality.com/shield/watch-state.svg" alt="watch-state quality">
|
|
@@ -62,7 +68,7 @@
|
|
|
62
68
|
<a href="https://github.com/d8corp/watch-state/blob/master/LICENSE" target="_blank">
|
|
63
69
|
<img src="https://img.shields.io/npm/l/watch-state" alt="watch-state license">
|
|
64
70
|
</a>
|
|
65
|
-
<a href="https://
|
|
71
|
+
<a href="https://github.com/d8corp/watch-state/blob/master/CHANGELOG.md" target="_blank">
|
|
66
72
|
<img src="https://img.shields.io/badge/Changelog-⋮-brightgreen" alt="watch-state changelog">
|
|
67
73
|
</a>
|
|
68
74
|
<a href="https://d8corp.github.io/watch-state/coverage/lcov-report" target="_blank">
|
|
@@ -73,16 +79,26 @@
|
|
|
73
79
|
|
|
74
80
|
`watch-state` is a **lightweight, high-performance reactive state engine** designed to power UI frameworks — **or replace them.**
|
|
75
81
|
|
|
76
|
-
|
|
82
|
+
- **Fast** — One of the fastest reactive libraries ([see benchmarks](#performance))
|
|
83
|
+
- **Light** — Less than 1 KB minzip
|
|
84
|
+
- **Zero-dependency** — No external packages required
|
|
85
|
+
- **Code splitting by design** — Decentralized state architecture, each page loads only the states it uses
|
|
86
|
+
- **Auto-subscription** — Dependencies tracked automatically, no manual subscriptions
|
|
87
|
+
- **Dynamic subscriptions** — Conditional watchers auto-subscribe/unsubscribe based on reactive conditions
|
|
88
|
+
- **Type-safe** — Full TypeScript support with type inference
|
|
89
|
+
- **Memory-safe** — Automatic cleanup on destroy
|
|
90
|
+
- **Lazy computation** — Compute executes only when accessed
|
|
91
|
+
- **No Proxy** — Supports old browsers (Firefox 45+, Safari 9+)
|
|
92
|
+
- **Framework-agnostic** — Business logic lives outside components, reusable across any framework or vanilla JS
|
|
77
93
|
|
|
78
94
|
Use it as the core state layer in your own framework, embed it in React components, or build a full UI — **no JSX, no virtual DOM, no framework required**.
|
|
79
95
|
|
|
80
|
-
|
|
96
|
+
Born while working on [@innet/dom](https://www.npmjs.com/package/@innet/dom).
|
|
81
97
|
|
|
82
98
|
[](https://github.com/d8corp/watch-state/stargazers)
|
|
83
99
|
[](https://github.com/d8corp/watch-state/watchers)
|
|
84
100
|
|
|
85
|
-
## Browser
|
|
101
|
+
## Browser Support
|
|
86
102
|
|
|
87
103
|
### Desktop
|
|
88
104
|
|
|
@@ -96,17 +112,17 @@ Was born during working on [@innet/dom](https://www.npmjs.com/package/@innet/dom
|
|
|
96
112
|
|:-------:|:------:|:------:|:-----:|
|
|
97
113
|
| 87+ | 90+ | 9+ | 62+ |
|
|
98
114
|
|
|
99
|
-
*You can transpile
|
|
115
|
+
*You can transpile the code to support browsers older than listed above, but performance will decrease.*
|
|
100
116
|
|
|
101
117
|
## Index
|
|
102
118
|
|
|
103
119
|
<sup>**[ [Install](#install) ]**</sup>
|
|
104
|
-
<sup>**[ [Usage](#usage) ]** [Simple example](#simple-example) • [Example Vanilla JS](#example-vanilla-js) • [Example React](#example-react) • [Example
|
|
105
|
-
<sup>**[ [Watch](#watch) ]** [
|
|
106
|
-
<sup>**[ [State](#state) ]** [Get or Set value](#get-or-set-value) • [Force update of State](#force-update-of-state) • [Raw value](#raw-value)</sup>
|
|
120
|
+
<sup>**[ [Usage](#usage) ]** [Simple example](#simple-example) • [Example Vanilla JS](#example-vanilla-js) • [Example React](#example-react) • [Example Innet](#example-innet)</sup>
|
|
121
|
+
<sup>**[ [Watch](#watch) ]** [Force update of Watch](#force-update-of-watch) • [Destroy Watch](#destroy-watch) • [Deep/Nested Watchers](#deepnested-watchers)</sup>
|
|
122
|
+
<sup>**[ [State](#state) ]** [Get or Set value](#get-or-set-value) • [State.set](#stateset) • [Force update of State](#force-update-of-state) • [Raw value](#raw-value) • [Initial value](#initial-value) • [Reset value](#reset-value)</sup>
|
|
107
123
|
<sup>**[ [Compute](#compute) ]** [Lazy computation](#lazy-computation) • [Force update of Compute](#force-update-of-compute) • [Destroy Compute](#destroy-compute)</sup>
|
|
108
124
|
<sup>**[ [Utils](#utils) ]** [onDestroy](#ondestroy) • [callEvent](#callevent) • [createEvent](#createevent) • [unwatch](#unwatch)</sup>
|
|
109
|
-
<sup>**[ [Typescript](#typescript) ]
|
|
125
|
+
<sup>**[ [Typescript](#typescript) ]** [State type inference](#state-type-inference) • [Compute type inference](#compute-type-inference)</sup>
|
|
110
126
|
<sup>**[ [Performance](#performance) ]**</sup>
|
|
111
127
|
|
|
112
128
|
## Install
|
|
@@ -132,7 +148,7 @@ html
|
|
|
132
148
|
## Usage
|
|
133
149
|
###### [🏠︎](#index) / Usage [↑](#install) [↓](#watch)
|
|
134
150
|
|
|
135
|
-
<sup>[Simple example](#simple-example) • [Example Vanilla JS](#example-vanilla-js) • [Example React](#example-react) • [Example
|
|
151
|
+
<sup>[Simple example](#simple-example) • [Example Vanilla JS](#example-vanilla-js) • [Example React](#example-react) • [Example Innet](#example-innet)</sup>
|
|
136
152
|
|
|
137
153
|
The library is based on the core concepts of `Observable` (something that can be observed) and `Observer` (something that can observe). On top of these concepts, the core classes `State`, `Compute`, and `Watch` are built according to the following scheme:
|
|
138
154
|
|
|
@@ -158,13 +174,13 @@ import { Watch, State } from 'watch-state'
|
|
|
158
174
|
const count = new State(0)
|
|
159
175
|
|
|
160
176
|
new Watch(() => console.log(count.value))
|
|
161
|
-
//
|
|
177
|
+
// logs: 0
|
|
162
178
|
|
|
163
179
|
count.value++
|
|
164
|
-
//
|
|
180
|
+
// logs: 1
|
|
165
181
|
|
|
166
182
|
count.value++
|
|
167
|
-
//
|
|
183
|
+
// logs: 2
|
|
168
184
|
```
|
|
169
185
|
|
|
170
186
|
### Example Vanilla JS
|
|
@@ -202,9 +218,9 @@ Simple reactive state without build tools or framework dependencies.
|
|
|
202
218
|
```
|
|
203
219
|
|
|
204
220
|
### Example React
|
|
205
|
-
###### [🏠︎](#index) / [Usage](#usage) / Example React [↑](#example-vanilla-js) [↓](#example-
|
|
221
|
+
###### [🏠︎](#index) / [Usage](#usage) / Example React [↑](#example-vanilla-js) [↓](#example-innet)
|
|
206
222
|
|
|
207
|
-
[@watch-state/react](https://www.npmjs.com/package/@watch-state/react) provides
|
|
223
|
+
[@watch-state/react](https://www.npmjs.com/package/@watch-state/react) provides hooks that automatically subscribe React components to state changes and re-renders only when needed.
|
|
208
224
|
|
|
209
225
|
```tsx
|
|
210
226
|
import { State } from 'watch-state'
|
|
@@ -223,12 +239,10 @@ export function CountButton () {
|
|
|
223
239
|
}
|
|
224
240
|
```
|
|
225
241
|
|
|
226
|
-
### Example
|
|
227
|
-
###### [🏠︎](#index) / [Usage](#usage) / Example
|
|
242
|
+
### Example Innet
|
|
243
|
+
###### [🏠︎](#index) / [Usage](#usage) / Example Innet [↑](#example-react)
|
|
228
244
|
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
`@innet/dom` automatically watches accessed states and **updates only changed DOM content** — **no full re-renders**.
|
|
245
|
+
[@innet/dom](https://www.npmjs.com/package/@innet/dom) automatically watches accessed states and **updates only changed DOM content** — **no full re-renders**.
|
|
232
246
|
|
|
233
247
|
```tsx
|
|
234
248
|
import { State } from 'watch-state'
|
|
@@ -244,87 +258,33 @@ export function CountButton () {
|
|
|
244
258
|
}
|
|
245
259
|
```
|
|
246
260
|
|
|
247
|
-
Key benefits:
|
|
248
|
-
|
|
249
|
-
- No Watch or useWatch needed — framework handles reactivity
|
|
250
|
-
- Only button content updates, no re-renders of component/DOM tree
|
|
251
|
-
- Direct state access {count} auto-triggers minimal updates
|
|
252
|
-
- Works with any JSX/TSX without extra setup
|
|
253
|
-
|
|
254
261
|
## Watch
|
|
255
262
|
###### [🏠︎](#index) / Watch [↑](#usage) [↓](#state)
|
|
256
263
|
|
|
257
|
-
<sup>[
|
|
258
|
-
|
|
259
|
-
**Reactive effect that automatically tracks and reacts to state changes.**
|
|
264
|
+
<sup>[Force update of Watch](#force-update-of-watch) • [Destroy Watch](#destroy-watch) • [Deep/Nested watchers](#deepnested-watchers)</sup>
|
|
260
265
|
|
|
261
|
-
`Watch`
|
|
262
|
-
|
|
266
|
+
`Watch` accepts a **reaction** as its first argument and executes it when any accessed state changes.
|
|
267
|
+
State accessed inside a reaction is **auto-subscribed** — no manual registration needed.
|
|
263
268
|
|
|
264
269
|
```ts
|
|
265
|
-
|
|
266
|
-
const count = new State(0)
|
|
267
|
-
|
|
268
|
-
// Create watcher that logs the state changes
|
|
269
|
-
new Watch(() => console.log(count.value)) // auto-subscribes to count
|
|
270
|
-
|
|
271
|
-
count.value = 1 // triggers watcher callback
|
|
272
|
-
```
|
|
273
|
-
|
|
274
|
-
### Update argument
|
|
275
|
-
###### [🏠︎](#index) / [Watch](#watch) / Update argument [↓](#force-update-of-watch)
|
|
276
|
-
|
|
277
|
-
**Distinguish initial run from updates using `update` parameter.**
|
|
278
|
-
|
|
279
|
-
`update` is `false` on **first execution** (initial subscription), `true` on **subsequent re-runs** when states change.
|
|
280
|
-
|
|
281
|
-
```javascript
|
|
282
|
-
const count = new State(0)
|
|
283
|
-
|
|
284
|
-
new Watch(update => {
|
|
285
|
-
console.log(update, count.value)
|
|
286
|
-
})
|
|
287
|
-
// console.log(false, 0)
|
|
288
|
-
|
|
289
|
-
count.value++
|
|
290
|
-
// console.log(true, 1)
|
|
291
|
-
|
|
292
|
-
count.value++
|
|
293
|
-
// console.log(true, 2)
|
|
294
|
-
```
|
|
295
|
-
|
|
296
|
-
**Watch state once using `update` flag and auto-destroy:**
|
|
297
|
-
|
|
298
|
-
```typescript jsx
|
|
299
|
-
const count = new State(0)
|
|
300
|
-
|
|
301
|
-
new Watch(update => {
|
|
302
|
-
|
|
303
|
-
if (!update) {
|
|
304
|
-
|
|
305
|
-
// Watch this value
|
|
306
|
-
count.value
|
|
307
|
-
|
|
308
|
-
} else {
|
|
309
|
-
|
|
310
|
-
// React on changes
|
|
311
|
-
console.log('The value was changed')
|
|
312
|
-
|
|
313
|
-
}
|
|
270
|
+
const state = new State(0)
|
|
314
271
|
|
|
315
|
-
|
|
272
|
+
const reaction = () => {
|
|
273
|
+
console.log(state.value)
|
|
274
|
+
// auto-subscribes to state
|
|
275
|
+
}
|
|
316
276
|
|
|
317
|
-
|
|
318
|
-
//
|
|
277
|
+
new Watch(reaction)
|
|
278
|
+
// logs: 0
|
|
319
279
|
|
|
320
|
-
|
|
321
|
-
//
|
|
280
|
+
state.value = 1 // triggers reaction
|
|
281
|
+
// logs: 1
|
|
322
282
|
```
|
|
323
283
|
|
|
324
284
|
### Force update of Watch
|
|
325
|
-
###### [🏠︎](#index) / [Watch](#watch) / Force update of Watch [
|
|
285
|
+
###### [🏠︎](#index) / [Watch](#watch) / Force update of Watch [↓](#destroy-watch)
|
|
326
286
|
|
|
327
|
-
You can run a
|
|
287
|
+
You can run a reaction even when its states are not updated.
|
|
328
288
|
|
|
329
289
|
```typescript
|
|
330
290
|
const count = new State(0)
|
|
@@ -332,10 +292,10 @@ const count = new State(0)
|
|
|
332
292
|
const watcher = new Watch(() => {
|
|
333
293
|
console.log(count.value)
|
|
334
294
|
})
|
|
335
|
-
//
|
|
295
|
+
// logs: 0
|
|
336
296
|
|
|
337
297
|
watcher.update()
|
|
338
|
-
//
|
|
298
|
+
// logs: 0
|
|
339
299
|
```
|
|
340
300
|
|
|
341
301
|
### Destroy Watch
|
|
@@ -349,10 +309,10 @@ const count = new State(0)
|
|
|
349
309
|
const watcher = new Watch(() => {
|
|
350
310
|
console.log(count.value)
|
|
351
311
|
})
|
|
352
|
-
//
|
|
312
|
+
// logs: 0
|
|
353
313
|
|
|
354
314
|
count.value++
|
|
355
|
-
//
|
|
315
|
+
// logs: 1
|
|
356
316
|
|
|
357
317
|
watcher.destroy()
|
|
358
318
|
|
|
@@ -360,12 +320,10 @@ count.value++
|
|
|
360
320
|
// nothing happens
|
|
361
321
|
```
|
|
362
322
|
|
|
363
|
-
### Deep/Nested
|
|
364
|
-
###### [🏠︎](#index) / [Watch](#watch) / Deep/Nested
|
|
323
|
+
### Deep/Nested Watchers
|
|
324
|
+
###### [🏠︎](#index) / [Watch](#watch) / Deep/Nested Watchers [↑](#destroy-watch)
|
|
365
325
|
|
|
366
|
-
**
|
|
367
|
-
|
|
368
|
-
Each `Watch` **independently tracks only states accessed within its callback**.
|
|
326
|
+
Each `Watch` **independently tracks only states accessed within its reaction**.
|
|
369
327
|
Nested watchers created inside parent watchers form a **dependency tree** with separate reactivity.
|
|
370
328
|
|
|
371
329
|
```javascript
|
|
@@ -396,14 +354,14 @@ state.value++
|
|
|
396
354
|
## State
|
|
397
355
|
###### [🏠︎](#index) / State [↑](#watch) [↓](#compute)
|
|
398
356
|
|
|
399
|
-
<sup>[Get or Set value](#get-or-set-value) • [Force update of State](#force-update-of-state) • [Raw value](#raw-value)</sup>
|
|
357
|
+
<sup>[Get or Set value](#get-or-set-value) • [State.set](#stateset) • [Force update of State](#force-update-of-state) • [Raw value](#raw-value) • [Initial value](#initial-value) • [Reset value](#reset-value)</sup>
|
|
400
358
|
|
|
401
|
-
**Reactive primitive** that automatically notifies all
|
|
359
|
+
**Reactive primitive** that holds a value and automatically notifies all subscribers when it changes.
|
|
402
360
|
|
|
403
361
|
### Get or Set value
|
|
404
|
-
###### [🏠︎](#index) / [State](#state) / Get or Set value [↓](#
|
|
362
|
+
###### [🏠︎](#index) / [State](#state) / Get or Set value [↓](#stateset)
|
|
405
363
|
|
|
406
|
-
|
|
364
|
+
Reading `.value` inside reaction **auto-subscribes** to changes. Writing `.value` **triggers all reactions**.
|
|
407
365
|
|
|
408
366
|
```ts
|
|
409
367
|
const count = new State(0)
|
|
@@ -414,34 +372,55 @@ new Watch(() => console.log(count.value))
|
|
|
414
372
|
count.value++ // triggers: logs 1
|
|
415
373
|
```
|
|
416
374
|
|
|
375
|
+
### State.set
|
|
376
|
+
###### [🏠︎](#index) / [State](#state) / State.set [↑](#get-or-set-value) [↓](#force-update-of-state)
|
|
377
|
+
|
|
378
|
+
`State.set` mirrors the behavior of the value setter but returns `void`.
|
|
379
|
+
It is useful as a shorthand in arrow functions: `() => state.set(nextValue)` instead of `() => { state.value = nextValue }`.
|
|
380
|
+
|
|
381
|
+
Note: `state.set` cannot be used as a standalone function; `const set = state.set` is not supported.
|
|
382
|
+
|
|
383
|
+
```ts
|
|
384
|
+
const count = new State(0)
|
|
385
|
+
|
|
386
|
+
// Subscribing
|
|
387
|
+
new Watch(() => console.log(count.value))
|
|
388
|
+
// logs: 0
|
|
389
|
+
|
|
390
|
+
count.set(1)
|
|
391
|
+
// logs: 1
|
|
392
|
+
```
|
|
393
|
+
|
|
417
394
|
### Force update of State
|
|
418
|
-
###### [🏠︎](#index) / [State](#state) / Force update of State [↑](#
|
|
395
|
+
###### [🏠︎](#index) / [State](#state) / Force update of State [↑](#stateset) [↓](#raw-value)
|
|
419
396
|
|
|
420
|
-
You can run
|
|
397
|
+
You can run reactions of a state with `update` method.
|
|
421
398
|
|
|
422
399
|
```ts
|
|
423
400
|
// Create state
|
|
424
|
-
const log = new State([])
|
|
401
|
+
const log = new State<number[]>([])
|
|
425
402
|
|
|
426
403
|
// Subscribe to changes
|
|
427
404
|
new Watch(() => console.log(log.value)) // logs: []
|
|
428
405
|
|
|
406
|
+
// Modify the array
|
|
429
407
|
log.value.push(1) // no logs
|
|
408
|
+
log.value.push(2) // no logs
|
|
430
409
|
|
|
431
410
|
// Update value
|
|
432
|
-
log.update() // logs: [1]
|
|
411
|
+
log.update() // logs: [1, 2]
|
|
433
412
|
```
|
|
434
413
|
|
|
435
414
|
### Raw value
|
|
436
|
-
###### [🏠︎](#index) / [State](#state) / Raw value [↑](#force-update-of-state)
|
|
415
|
+
###### [🏠︎](#index) / [State](#state) / Raw value [↑](#force-update-of-state) [↓](#initial-value)
|
|
437
416
|
|
|
438
|
-
`
|
|
417
|
+
`raw` returns the current value but does not subscribe to changes — unlike `value`.
|
|
439
418
|
|
|
440
419
|
```ts
|
|
441
420
|
const foo = new State(0)
|
|
442
421
|
const bar = new State(0)
|
|
443
422
|
|
|
444
|
-
new Watch(() => console.log(foo.value, bar.
|
|
423
|
+
new Watch(() => console.log(foo.value, bar.raw))
|
|
445
424
|
// logs: 0, 0
|
|
446
425
|
|
|
447
426
|
foo.value++ // logs: 1, 0
|
|
@@ -449,19 +428,62 @@ bar.value++ // no logs
|
|
|
449
428
|
foo.value++ // logs: 2, 1
|
|
450
429
|
```
|
|
451
430
|
|
|
431
|
+
### Initial value
|
|
432
|
+
###### [🏠︎](#index) / [State](#state) / Initial value [↑](#raw-value) [↓](#reset-value)
|
|
433
|
+
|
|
434
|
+
`initial` stores the initial value passed to the constructor.
|
|
435
|
+
Useful for checking if the state has been modified by comparing `state.initial === state.raw`.
|
|
436
|
+
|
|
437
|
+
```ts
|
|
438
|
+
const count = new State(0)
|
|
439
|
+
|
|
440
|
+
console.log(count.initial)
|
|
441
|
+
// logs: 0
|
|
442
|
+
|
|
443
|
+
count.value = 5
|
|
444
|
+
console.log(count.initial === count.raw)
|
|
445
|
+
// logs: false
|
|
446
|
+
|
|
447
|
+
count.reset()
|
|
448
|
+
console.log(count.initial === count.raw)
|
|
449
|
+
// logs: true
|
|
450
|
+
```
|
|
451
|
+
|
|
452
|
+
### Reset value
|
|
453
|
+
###### [🏠︎](#index) / [State](#state) / Reset value [↑](#initial-value)
|
|
454
|
+
|
|
455
|
+
`reset()` restores the state to its initial value.
|
|
456
|
+
Triggers watchers only if the current value differs from the initial value.
|
|
457
|
+
|
|
458
|
+
```ts
|
|
459
|
+
const count = new State(0)
|
|
460
|
+
|
|
461
|
+
new Watch(() => console.log(count.value))
|
|
462
|
+
// logs: 0
|
|
463
|
+
|
|
464
|
+
count.value = 5
|
|
465
|
+
// logs: 5
|
|
466
|
+
|
|
467
|
+
count.reset()
|
|
468
|
+
// logs: 0
|
|
469
|
+
|
|
470
|
+
count.reset()
|
|
471
|
+
// no logs (value already 0)
|
|
472
|
+
```
|
|
473
|
+
|
|
452
474
|
## Compute
|
|
453
475
|
###### [🏠︎](#index) / Compute [↑](#state) [↓](#utils)
|
|
454
476
|
|
|
455
477
|
<sup>[Lazy computation](#lazy-computation) • [Force update of Compute](#force-update-of-compute) • [Destroy Compute](#destroy-compute)</sup>
|
|
456
478
|
|
|
457
|
-
|
|
458
|
-
**
|
|
479
|
+
`Compute` accepts a **reaction** as its first argument and represents a reactive value returned by the reaction.
|
|
480
|
+
It creates a **derived state** that automatically tracks dependencies and caches the result.
|
|
459
481
|
|
|
460
482
|
### Lazy computation
|
|
461
483
|
###### [🏠︎](#index) / [Compute](#compute) / Lazy computation [↓](#force-update-of-compute)
|
|
462
484
|
|
|
463
485
|
`Compute` doesn't execute immediately — waits for `.value` access.
|
|
464
|
-
Dependencies (`State.value` reads inside
|
|
486
|
+
Dependencies (`State.value` reads inside reaction) auto-subscribe like `Watch`.
|
|
465
487
|
|
|
466
488
|
```javascript
|
|
467
489
|
const name = new State('Foo')
|
|
@@ -484,60 +506,41 @@ surname.value = 'Quux' // surname[0] = "Q"
|
|
|
484
506
|
// logs: 'Foo Q'
|
|
485
507
|
```
|
|
486
508
|
|
|
487
|
-
**Benefits:**
|
|
488
|
-
- **Zero overhead** for unused computed values
|
|
489
|
-
- **Automatic dependency tracking** — no manual subscriptions
|
|
490
|
-
- **Cached result** — same `.value` reads return cached value
|
|
491
|
-
|
|
492
509
|
### Force update of Compute
|
|
493
510
|
###### [🏠︎](#index) / [Compute](#compute) / Force update of Compute [↑](#lazy-computation) [↓](#destroy-compute)
|
|
494
511
|
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
**Perfect for:**
|
|
498
|
-
- **Array mutations** (`push`, `pop`, `splice`)
|
|
499
|
-
- **Object mutations** (adding properties)
|
|
500
|
-
- **External data refresh**
|
|
501
|
-
- **Debugging** stale values
|
|
512
|
+
You can run a reaction of a compute with `update` method.
|
|
502
513
|
|
|
503
514
|
```ts
|
|
504
515
|
const items = new State([])
|
|
505
516
|
|
|
506
517
|
const itemCount = new Compute(() => {
|
|
507
|
-
console.log('
|
|
518
|
+
console.log('Recomputing length...')
|
|
508
519
|
return items.value.length
|
|
509
520
|
})
|
|
510
521
|
|
|
511
522
|
new Watch(() => console.log('Watcher sees:', itemCount.value))
|
|
512
|
-
//
|
|
513
|
-
// Watcher sees: 0
|
|
523
|
+
// logs: Recomputing length...
|
|
524
|
+
// logs: Watcher sees: 0
|
|
514
525
|
|
|
515
|
-
items.value.push('apple')
|
|
516
|
-
|
|
517
|
-
console.log(itemCount.value) // STALE: 0 ❌
|
|
526
|
+
items.value.push('apple')
|
|
527
|
+
// Array reference SAME → NO recompute!
|
|
518
528
|
|
|
519
|
-
itemCount.update()
|
|
520
|
-
//
|
|
521
|
-
// Watcher sees: 1
|
|
529
|
+
itemCount.update()
|
|
530
|
+
// logs: Recomputing length...
|
|
531
|
+
// logs: Watcher sees: 1
|
|
522
532
|
```
|
|
523
533
|
|
|
524
534
|
### Destroy Compute
|
|
525
535
|
###### [🏠︎](#index) / [Compute](#compute) / Destroy Compute [↑](#force-update-of-compute)
|
|
526
536
|
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
Triggers `onDestroy` callbacks registered inside `Compute` callback:
|
|
537
|
+
You can stop watching by `destroy` method of `Compute`.
|
|
530
538
|
|
|
531
539
|
```ts
|
|
532
540
|
const user = new State({ name: 'Alice', age: 30 })
|
|
533
541
|
|
|
534
542
|
const userName = new Compute(() => {
|
|
535
543
|
console.log('Computing')
|
|
536
|
-
|
|
537
|
-
onDestroy(() => {
|
|
538
|
-
console.log('Cleanup')
|
|
539
|
-
})
|
|
540
|
-
|
|
541
544
|
return user.value.name.toUpperCase()
|
|
542
545
|
})
|
|
543
546
|
|
|
@@ -546,12 +549,10 @@ new Watch(() => console.log(userName.value))
|
|
|
546
549
|
// logs: ALICE
|
|
547
550
|
|
|
548
551
|
user.value = { name: 'Mike', age: 32 }
|
|
549
|
-
// logs: Cleanup
|
|
550
552
|
// logs: Computing
|
|
551
553
|
// logs: MIKE
|
|
552
554
|
|
|
553
555
|
userName.destroy()
|
|
554
|
-
// logs: Cleanup
|
|
555
556
|
|
|
556
557
|
user.value = { name: 'Bob', age: 31 }
|
|
557
558
|
// nothing happens — fully disconnected!
|
|
@@ -591,23 +592,16 @@ count.value++
|
|
|
591
592
|
### callEvent
|
|
592
593
|
###### [🏠︎](#index) / [Utils](#utils) / callEvent [↑](#ondestroy) [↓](#createevent)
|
|
593
594
|
|
|
594
|
-
|
|
595
|
+
You can immediately execute a reactive effect with `callEvent`.
|
|
595
596
|
|
|
596
|
-
|
|
597
|
-
- **Ignores** automatic state subscriptions (`unwatch`)
|
|
598
|
-
- **Batches** state updates and **flushes queue** at the end
|
|
599
|
-
- Perfect for **side effects** and **mutations**
|
|
600
|
-
|
|
601
|
-
**Key differences:**
|
|
602
|
-
- `callEvent(fn)` → **executes NOW** and returns result
|
|
603
|
-
- `createEvent(fn)` → **returns reusable function**
|
|
597
|
+
`callEvent` batches all state updates inside the callback and triggers watchers only once at the end.
|
|
604
598
|
|
|
605
599
|
```ts
|
|
606
600
|
const a = new State(0)
|
|
607
601
|
const b = new State(0)
|
|
608
602
|
|
|
609
603
|
new Watch(() => {
|
|
610
|
-
|
|
604
|
+
console.log(a.value, b.value)
|
|
611
605
|
})
|
|
612
606
|
// logs: 0, 0
|
|
613
607
|
|
|
@@ -618,14 +612,13 @@ b.value = 1
|
|
|
618
612
|
// logs: 1, 1
|
|
619
613
|
|
|
620
614
|
callEvent(() => {
|
|
621
|
-
|
|
622
|
-
|
|
615
|
+
a.value = 2
|
|
616
|
+
b.value = 2
|
|
623
617
|
})
|
|
624
|
-
// logs:
|
|
618
|
+
// logs: 2, 2
|
|
625
619
|
```
|
|
626
620
|
|
|
627
|
-
`callEvent`
|
|
628
|
-
returns — TypeScript infers the correct type automatically.
|
|
621
|
+
`callEvent` returns exactly what your callback returns — TypeScript infers the correct type automatically.
|
|
629
622
|
|
|
630
623
|
```ts
|
|
631
624
|
const count = new State(0)
|
|
@@ -643,31 +636,30 @@ console.log(prev)
|
|
|
643
636
|
### createEvent
|
|
644
637
|
###### [🏠︎](#index) / [Utils](#utils) / createEvent [↑](#callevent) [↓](#unwatch)
|
|
645
638
|
|
|
646
|
-
You can create event function with `createEvent
|
|
639
|
+
You can create a reusable event function with `createEvent`.
|
|
640
|
+
|
|
641
|
+
Like `callEvent`, it batches state updates and triggers watchers only once after execution.
|
|
642
|
+
|
|
647
643
|
```typescript
|
|
648
644
|
import { State, createEvent } from 'watch-state'
|
|
649
645
|
|
|
650
646
|
const count = new State(0)
|
|
651
|
-
const increase = createEvent(() =>
|
|
652
|
-
console.log(count.value++)
|
|
653
|
-
})
|
|
647
|
+
const increase = createEvent(() => count.value++)
|
|
654
648
|
|
|
655
649
|
new Watch(() => console.log(count.value))
|
|
656
|
-
//
|
|
650
|
+
// logs: 0
|
|
657
651
|
|
|
658
652
|
increase()
|
|
659
|
-
//
|
|
653
|
+
// logs: 1
|
|
660
654
|
|
|
661
655
|
increase()
|
|
662
|
-
//
|
|
656
|
+
// logs: 2
|
|
663
657
|
```
|
|
664
658
|
|
|
665
659
|
### unwatch
|
|
666
660
|
###### [🏠︎](#index) / [Utils](#utils) / unwatch [↑](#createevent)
|
|
667
661
|
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
**Unlike `callEvent`/`createEvent`**, `unwatch` does **NOT batch updates**.
|
|
662
|
+
You can disable automatic state subscriptions with `unwatch`.
|
|
671
663
|
|
|
672
664
|
```ts
|
|
673
665
|
import { State, Watch, unwatch } from 'watch-state'
|
|
@@ -676,29 +668,86 @@ const count = new State(0)
|
|
|
676
668
|
|
|
677
669
|
new Watch(() => {
|
|
678
670
|
console.log(unwatch(() => count.value++))
|
|
679
|
-
})
|
|
671
|
+
})
|
|
672
|
+
// logs: 0
|
|
680
673
|
|
|
681
|
-
count.value++
|
|
674
|
+
count.value++
|
|
675
|
+
// logs: 1
|
|
682
676
|
|
|
683
|
-
console.log(count.value)
|
|
677
|
+
console.log(count.value)
|
|
678
|
+
// logs: 2
|
|
684
679
|
```
|
|
685
680
|
|
|
686
681
|
## Typescript
|
|
687
682
|
###### [🏠︎](#index) / Typescript [↑](#utils) [↓](#performance)
|
|
688
683
|
|
|
689
|
-
|
|
684
|
+
### State type inference
|
|
685
|
+
###### [🏠︎](#index) / [Typescript](#typescript) / State type inference [↓](#compute-type-inference)
|
|
686
|
+
|
|
687
|
+
**Type inference from initial value:**
|
|
688
|
+
Type is automatically inferred from the initial value passed to the constructor — no generic needed.
|
|
690
689
|
```typescript
|
|
691
|
-
const
|
|
690
|
+
const count = new State(0) // State<number>
|
|
692
691
|
|
|
693
|
-
|
|
694
|
-
// error, you can use only string or number
|
|
692
|
+
count.value = 'str' // error: number expected
|
|
695
693
|
```
|
|
696
|
-
|
|
694
|
+
|
|
695
|
+
**Without initial value:**
|
|
696
|
+
When using a generic without an initial value, `initial` is `undefined`, which may conflict with strict types.
|
|
697
|
+
|
|
697
698
|
```typescript
|
|
698
|
-
new
|
|
699
|
-
//
|
|
699
|
+
const value = new State<string>()
|
|
700
|
+
// value.initial is undefined (not string)
|
|
701
|
+
|
|
702
|
+
// To allow undefined in type:
|
|
703
|
+
const maybe = new State<string | undefined>()
|
|
700
704
|
```
|
|
701
705
|
|
|
706
|
+
**State as a type annotation:**
|
|
707
|
+
Without a generic, `State` defaults to `State<unknown>`, which accepts any value type.
|
|
708
|
+
|
|
709
|
+
```typescript
|
|
710
|
+
const foo: State = new State(0)
|
|
711
|
+
|
|
712
|
+
foo.value = 'str' // ok (unknown allows any)
|
|
713
|
+
foo.value = true // ok
|
|
714
|
+
|
|
715
|
+
// Specify generic for type safety:
|
|
716
|
+
const bar: State<number> = new State(0)
|
|
717
|
+
|
|
718
|
+
bar.value = 'str' // error
|
|
719
|
+
```
|
|
720
|
+
|
|
721
|
+
### Compute type inference
|
|
722
|
+
###### [🏠︎](#index) / [Typescript](#typescript) / Compute type inference [↑](#state-type-inference)
|
|
723
|
+
|
|
724
|
+
**Type inferred from function return:**
|
|
725
|
+
Type is automatically inferred from the function's return value — no generic needed.
|
|
726
|
+
```typescript
|
|
727
|
+
const fullName = new Compute(() => `${firstName.value} ${lastName.value}`)
|
|
728
|
+
// Compute<string> — no generic needed
|
|
729
|
+
|
|
730
|
+
const length = new Compute(() => items.value.length)
|
|
731
|
+
// Compute<number>
|
|
732
|
+
```
|
|
733
|
+
|
|
734
|
+
**Explicit generic (usually not needed):**
|
|
735
|
+
Explicit generics are rarely needed since types are inferred. Use only when you want to enforce a specific type.
|
|
736
|
+
```typescript
|
|
737
|
+
new Compute<string>(() => false) // error: boolean not assignable to string
|
|
738
|
+
```
|
|
739
|
+
|
|
740
|
+
**Destroyed Compute and undefined:**
|
|
741
|
+
`Compute.value` is typed as the function return type, but if you access `.value` after `destroy()` (before any computation ran), it returns `undefined`.
|
|
742
|
+
```typescript
|
|
743
|
+
const computed = new Compute(() => expensiveCalculation())
|
|
744
|
+
|
|
745
|
+
computed.destroy()
|
|
746
|
+
console.log(computed.value) // undefined (but typed as return type)
|
|
747
|
+
```
|
|
748
|
+
|
|
749
|
+
This is intentional — accessing destroyed observers is rare and shouldn't require `undefined` checks in normal code.
|
|
750
|
+
|
|
702
751
|
## Performance
|
|
703
752
|
###### [🏠︎](#index) / Performance [↑](#typescript)
|
|
704
753
|
|