watch-state 3.5.2 → 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 +218 -189
- package/State/State.d.ts +32 -6
- package/State/State.es6.js +19 -4
- package/State/State.js +19 -4
- 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) • [
|
|
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,16 +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) • [
|
|
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 [↓](#
|
|
405
|
-
|
|
406
|
-
**Access or mutate the state value.**
|
|
362
|
+
###### [🏠︎](#index) / [State](#state) / Get or Set value [↓](#stateset)
|
|
407
363
|
|
|
408
|
-
Reading `.value` inside
|
|
364
|
+
Reading `.value` inside reaction **auto-subscribes** to changes. Writing `.value` **triggers all reactions**.
|
|
409
365
|
|
|
410
366
|
```ts
|
|
411
367
|
const count = new State(0)
|
|
@@ -416,34 +372,55 @@ new Watch(() => console.log(count.value))
|
|
|
416
372
|
count.value++ // triggers: logs 1
|
|
417
373
|
```
|
|
418
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
|
+
|
|
419
394
|
### Force update of State
|
|
420
|
-
###### [🏠︎](#index) / [State](#state) / Force update of State [↑](#
|
|
395
|
+
###### [🏠︎](#index) / [State](#state) / Force update of State [↑](#stateset) [↓](#raw-value)
|
|
421
396
|
|
|
422
|
-
You can run
|
|
397
|
+
You can run reactions of a state with `update` method.
|
|
423
398
|
|
|
424
399
|
```ts
|
|
425
400
|
// Create state
|
|
426
|
-
const log = new State([])
|
|
401
|
+
const log = new State<number[]>([])
|
|
427
402
|
|
|
428
403
|
// Subscribe to changes
|
|
429
404
|
new Watch(() => console.log(log.value)) // logs: []
|
|
430
405
|
|
|
406
|
+
// Modify the array
|
|
431
407
|
log.value.push(1) // no logs
|
|
408
|
+
log.value.push(2) // no logs
|
|
432
409
|
|
|
433
410
|
// Update value
|
|
434
|
-
log.update() // logs: [1]
|
|
411
|
+
log.update() // logs: [1, 2]
|
|
435
412
|
```
|
|
436
413
|
|
|
437
414
|
### Raw value
|
|
438
|
-
###### [🏠︎](#index) / [State](#state) / Raw value [↑](#force-update-of-state) [↓](#
|
|
415
|
+
###### [🏠︎](#index) / [State](#state) / Raw value [↑](#force-update-of-state) [↓](#initial-value)
|
|
439
416
|
|
|
440
|
-
`
|
|
417
|
+
`raw` returns the current value but does not subscribe to changes — unlike `value`.
|
|
441
418
|
|
|
442
419
|
```ts
|
|
443
420
|
const foo = new State(0)
|
|
444
421
|
const bar = new State(0)
|
|
445
422
|
|
|
446
|
-
new Watch(() => console.log(foo.value, bar.
|
|
423
|
+
new Watch(() => console.log(foo.value, bar.raw))
|
|
447
424
|
// logs: 0, 0
|
|
448
425
|
|
|
449
426
|
foo.value++ // logs: 1, 0
|
|
@@ -451,22 +428,47 @@ bar.value++ // no logs
|
|
|
451
428
|
foo.value++ // logs: 2, 1
|
|
452
429
|
```
|
|
453
430
|
|
|
454
|
-
###
|
|
455
|
-
###### [🏠︎](#index) / [State](#state) /
|
|
431
|
+
### Initial value
|
|
432
|
+
###### [🏠︎](#index) / [State](#state) / Initial value [↑](#raw-value) [↓](#reset-value)
|
|
456
433
|
|
|
457
|
-
`
|
|
458
|
-
|
|
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`.
|
|
459
436
|
|
|
460
|
-
|
|
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.
|
|
461
457
|
|
|
462
458
|
```ts
|
|
463
459
|
const count = new State(0)
|
|
464
460
|
|
|
465
|
-
// Subscribing
|
|
466
461
|
new Watch(() => console.log(count.value))
|
|
467
|
-
//
|
|
462
|
+
// logs: 0
|
|
463
|
+
|
|
464
|
+
count.value = 5
|
|
465
|
+
// logs: 5
|
|
466
|
+
|
|
467
|
+
count.reset()
|
|
468
|
+
// logs: 0
|
|
468
469
|
|
|
469
|
-
count.
|
|
470
|
+
count.reset()
|
|
471
|
+
// no logs (value already 0)
|
|
470
472
|
```
|
|
471
473
|
|
|
472
474
|
## Compute
|
|
@@ -474,14 +476,14 @@ count.set(1) // triggers: log: 1
|
|
|
474
476
|
|
|
475
477
|
<sup>[Lazy computation](#lazy-computation) • [Force update of Compute](#force-update-of-compute) • [Destroy Compute](#destroy-compute)</sup>
|
|
476
478
|
|
|
477
|
-
|
|
478
|
-
**
|
|
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.
|
|
479
481
|
|
|
480
482
|
### Lazy computation
|
|
481
483
|
###### [🏠︎](#index) / [Compute](#compute) / Lazy computation [↓](#force-update-of-compute)
|
|
482
484
|
|
|
483
485
|
`Compute` doesn't execute immediately — waits for `.value` access.
|
|
484
|
-
Dependencies (`State.value` reads inside
|
|
486
|
+
Dependencies (`State.value` reads inside reaction) auto-subscribe like `Watch`.
|
|
485
487
|
|
|
486
488
|
```javascript
|
|
487
489
|
const name = new State('Foo')
|
|
@@ -504,60 +506,41 @@ surname.value = 'Quux' // surname[0] = "Q"
|
|
|
504
506
|
// logs: 'Foo Q'
|
|
505
507
|
```
|
|
506
508
|
|
|
507
|
-
**Benefits:**
|
|
508
|
-
- **Zero overhead** for unused computed values
|
|
509
|
-
- **Automatic dependency tracking** — no manual subscriptions
|
|
510
|
-
- **Cached result** — same `.value` reads return cached value
|
|
511
|
-
|
|
512
509
|
### Force update of Compute
|
|
513
510
|
###### [🏠︎](#index) / [Compute](#compute) / Force update of Compute [↑](#lazy-computation) [↓](#destroy-compute)
|
|
514
511
|
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
**Perfect for:**
|
|
518
|
-
- **Array mutations** (`push`, `pop`, `splice`)
|
|
519
|
-
- **Object mutations** (adding properties)
|
|
520
|
-
- **External data refresh**
|
|
521
|
-
- **Debugging** stale values
|
|
512
|
+
You can run a reaction of a compute with `update` method.
|
|
522
513
|
|
|
523
514
|
```ts
|
|
524
515
|
const items = new State([])
|
|
525
516
|
|
|
526
517
|
const itemCount = new Compute(() => {
|
|
527
|
-
console.log('
|
|
518
|
+
console.log('Recomputing length...')
|
|
528
519
|
return items.value.length
|
|
529
520
|
})
|
|
530
521
|
|
|
531
522
|
new Watch(() => console.log('Watcher sees:', itemCount.value))
|
|
532
|
-
//
|
|
533
|
-
// Watcher sees: 0
|
|
523
|
+
// logs: Recomputing length...
|
|
524
|
+
// logs: Watcher sees: 0
|
|
534
525
|
|
|
535
|
-
items.value.push('apple')
|
|
536
|
-
|
|
537
|
-
console.log(itemCount.value) // STALE: 0 ❌
|
|
526
|
+
items.value.push('apple')
|
|
527
|
+
// Array reference SAME → NO recompute!
|
|
538
528
|
|
|
539
|
-
itemCount.update()
|
|
540
|
-
//
|
|
541
|
-
// Watcher sees: 1
|
|
529
|
+
itemCount.update()
|
|
530
|
+
// logs: Recomputing length...
|
|
531
|
+
// logs: Watcher sees: 1
|
|
542
532
|
```
|
|
543
533
|
|
|
544
534
|
### Destroy Compute
|
|
545
535
|
###### [🏠︎](#index) / [Compute](#compute) / Destroy Compute [↑](#force-update-of-compute)
|
|
546
536
|
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
Triggers `onDestroy` callbacks registered inside `Compute` callback:
|
|
537
|
+
You can stop watching by `destroy` method of `Compute`.
|
|
550
538
|
|
|
551
539
|
```ts
|
|
552
540
|
const user = new State({ name: 'Alice', age: 30 })
|
|
553
541
|
|
|
554
542
|
const userName = new Compute(() => {
|
|
555
543
|
console.log('Computing')
|
|
556
|
-
|
|
557
|
-
onDestroy(() => {
|
|
558
|
-
console.log('Cleanup')
|
|
559
|
-
})
|
|
560
|
-
|
|
561
544
|
return user.value.name.toUpperCase()
|
|
562
545
|
})
|
|
563
546
|
|
|
@@ -566,12 +549,10 @@ new Watch(() => console.log(userName.value))
|
|
|
566
549
|
// logs: ALICE
|
|
567
550
|
|
|
568
551
|
user.value = { name: 'Mike', age: 32 }
|
|
569
|
-
// logs: Cleanup
|
|
570
552
|
// logs: Computing
|
|
571
553
|
// logs: MIKE
|
|
572
554
|
|
|
573
555
|
userName.destroy()
|
|
574
|
-
// logs: Cleanup
|
|
575
556
|
|
|
576
557
|
user.value = { name: 'Bob', age: 31 }
|
|
577
558
|
// nothing happens — fully disconnected!
|
|
@@ -611,23 +592,16 @@ count.value++
|
|
|
611
592
|
### callEvent
|
|
612
593
|
###### [🏠︎](#index) / [Utils](#utils) / callEvent [↑](#ondestroy) [↓](#createevent)
|
|
613
594
|
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
Both `callEvent` and `createEvent`:
|
|
617
|
-
- **Ignores** automatic state subscriptions (`unwatch`)
|
|
618
|
-
- **Batches** state updates and **flushes queue** at the end
|
|
619
|
-
- Perfect for **side effects** and **mutations**
|
|
595
|
+
You can immediately execute a reactive effect with `callEvent`.
|
|
620
596
|
|
|
621
|
-
|
|
622
|
-
- `callEvent(fn)` → **executes NOW** and returns result
|
|
623
|
-
- `createEvent(fn)` → **returns reusable function**
|
|
597
|
+
`callEvent` batches all state updates inside the callback and triggers watchers only once at the end.
|
|
624
598
|
|
|
625
599
|
```ts
|
|
626
600
|
const a = new State(0)
|
|
627
601
|
const b = new State(0)
|
|
628
602
|
|
|
629
603
|
new Watch(() => {
|
|
630
|
-
|
|
604
|
+
console.log(a.value, b.value)
|
|
631
605
|
})
|
|
632
606
|
// logs: 0, 0
|
|
633
607
|
|
|
@@ -638,14 +612,13 @@ b.value = 1
|
|
|
638
612
|
// logs: 1, 1
|
|
639
613
|
|
|
640
614
|
callEvent(() => {
|
|
641
|
-
|
|
642
|
-
|
|
615
|
+
a.value = 2
|
|
616
|
+
b.value = 2
|
|
643
617
|
})
|
|
644
|
-
// logs:
|
|
618
|
+
// logs: 2, 2
|
|
645
619
|
```
|
|
646
620
|
|
|
647
|
-
`callEvent`
|
|
648
|
-
returns — TypeScript infers the correct type automatically.
|
|
621
|
+
`callEvent` returns exactly what your callback returns — TypeScript infers the correct type automatically.
|
|
649
622
|
|
|
650
623
|
```ts
|
|
651
624
|
const count = new State(0)
|
|
@@ -663,31 +636,30 @@ console.log(prev)
|
|
|
663
636
|
### createEvent
|
|
664
637
|
###### [🏠︎](#index) / [Utils](#utils) / createEvent [↑](#callevent) [↓](#unwatch)
|
|
665
638
|
|
|
666
|
-
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
|
+
|
|
667
643
|
```typescript
|
|
668
644
|
import { State, createEvent } from 'watch-state'
|
|
669
645
|
|
|
670
646
|
const count = new State(0)
|
|
671
|
-
const increase = createEvent(() =>
|
|
672
|
-
console.log(count.value++)
|
|
673
|
-
})
|
|
647
|
+
const increase = createEvent(() => count.value++)
|
|
674
648
|
|
|
675
649
|
new Watch(() => console.log(count.value))
|
|
676
|
-
//
|
|
650
|
+
// logs: 0
|
|
677
651
|
|
|
678
652
|
increase()
|
|
679
|
-
//
|
|
653
|
+
// logs: 1
|
|
680
654
|
|
|
681
655
|
increase()
|
|
682
|
-
//
|
|
656
|
+
// logs: 2
|
|
683
657
|
```
|
|
684
658
|
|
|
685
659
|
### unwatch
|
|
686
660
|
###### [🏠︎](#index) / [Utils](#utils) / unwatch [↑](#createevent)
|
|
687
661
|
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
**Unlike `callEvent`/`createEvent`**, `unwatch` does **NOT batch updates**.
|
|
662
|
+
You can disable automatic state subscriptions with `unwatch`.
|
|
691
663
|
|
|
692
664
|
```ts
|
|
693
665
|
import { State, Watch, unwatch } from 'watch-state'
|
|
@@ -696,29 +668,86 @@ const count = new State(0)
|
|
|
696
668
|
|
|
697
669
|
new Watch(() => {
|
|
698
670
|
console.log(unwatch(() => count.value++))
|
|
699
|
-
})
|
|
671
|
+
})
|
|
672
|
+
// logs: 0
|
|
700
673
|
|
|
701
|
-
count.value++
|
|
674
|
+
count.value++
|
|
675
|
+
// logs: 1
|
|
702
676
|
|
|
703
|
-
console.log(count.value)
|
|
677
|
+
console.log(count.value)
|
|
678
|
+
// logs: 2
|
|
704
679
|
```
|
|
705
680
|
|
|
706
681
|
## Typescript
|
|
707
682
|
###### [🏠︎](#index) / Typescript [↑](#utils) [↓](#performance)
|
|
708
683
|
|
|
709
|
-
|
|
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.
|
|
689
|
+
```typescript
|
|
690
|
+
const count = new State(0) // State<number>
|
|
691
|
+
|
|
692
|
+
count.value = 'str' // error: number expected
|
|
693
|
+
```
|
|
694
|
+
|
|
695
|
+
**Without initial value:**
|
|
696
|
+
When using a generic without an initial value, `initial` is `undefined`, which may conflict with strict types.
|
|
697
|
+
|
|
698
|
+
```typescript
|
|
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>()
|
|
704
|
+
```
|
|
705
|
+
|
|
706
|
+
**State as a type annotation:**
|
|
707
|
+
Without a generic, `State` defaults to `State<unknown>`, which accepts any value type.
|
|
708
|
+
|
|
710
709
|
```typescript
|
|
711
|
-
const
|
|
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)
|
|
712
717
|
|
|
713
|
-
|
|
714
|
-
// error, you can use only string or number
|
|
718
|
+
bar.value = 'str' // error
|
|
715
719
|
```
|
|
716
|
-
|
|
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.
|
|
717
726
|
```typescript
|
|
718
|
-
new Compute
|
|
719
|
-
//
|
|
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>
|
|
720
732
|
```
|
|
721
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
|
+
|
|
722
751
|
## Performance
|
|
723
752
|
###### [🏠︎](#index) / Performance [↑](#typescript)
|
|
724
753
|
|