-
Notifications
You must be signed in to change notification settings - Fork 4.7k
/
Copy pathreactivity-core.md
581 lines (421 loc) Β· 16.2 KB
/
reactivity-core.md
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
# Reactivity API: Core {#reactivity-api-core}
:::info See also
To better understand the Reactivity APIs, it is recommended to read the following chapters in the guide:
- [Reactivity Fundamentals](/guide/essentials/reactivity-fundamentals) (with the API preference set to Composition API)
- [Reactivity in Depth](/guide/extras/reactivity-in-depth)
:::
## ref() {#ref}
Takes an inner value and returns a reactive and mutable ref object, which has a single property `.value` that points to the inner value.
- **Type**
```ts
function ref<T>(value: T): Ref<UnwrapRef<T>>
interface Ref<T> {
value: T
}
```
- **Details**
The ref object is mutable - i.e. you can assign new values to `.value`. It is also reactive - i.e. any read operations to `.value` are tracked, and write operations will trigger associated effects.
If an object is assigned as a ref's value, the object is made deeply reactive with [reactive()](#reactive). This also means if the object contains nested refs, they will be deeply unwrapped.
To avoid the deep conversion, use [`shallowRef()`](./reactivity-advanced#shallowref) instead.
- **Example**
```js
const count = ref(0)
console.log(count.value) // 0
count.value = 1
console.log(count.value) // 1
```
- **See also**
- [Guide - Reactivity Fundamentals with `ref()`](/guide/essentials/reactivity-fundamentals#ref)
- [Guide - Typing `ref()`](/guide/typescript/composition-api#typing-ref) <sup class="vt-badge ts" />
## computed() {#computed}
Takes a [getter function](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/get#description) and returns a readonly reactive [ref](#ref) object for the returned value from the getter. It can also take an object with `get` and `set` functions to create a writable ref object.
- **Type**
```ts
// read-only
function computed<T>(
getter: (oldValue: T | undefined) => T,
// see "Computed Debugging" link below
debuggerOptions?: DebuggerOptions
): Readonly<Ref<Readonly<T>>>
// writable
function computed<T>(
options: {
get: (oldValue: T | undefined) => T
set: (value: T) => void
},
debuggerOptions?: DebuggerOptions
): Ref<T>
```
- **Example**
Creating a readonly computed ref:
```js
const count = ref(1)
const plusOne = computed(() => count.value + 1)
console.log(plusOne.value) // 2
plusOne.value++ // error
```
Creating a writable computed ref:
```js
const count = ref(1)
const plusOne = computed({
get: () => count.value + 1,
set: (val) => {
count.value = val - 1
}
})
plusOne.value = 1
console.log(count.value) // 0
```
Debugging:
```js
const plusOne = computed(() => count.value + 1, {
onTrack(e) {
debugger
},
onTrigger(e) {
debugger
}
})
```
- **See also**
- [Guide - Computed Properties](/guide/essentials/computed)
- [Guide - Computed Debugging](/guide/extras/reactivity-in-depth#computed-debugging)
- [Guide - Typing `computed()`](/guide/typescript/composition-api#typing-computed) <sup class="vt-badge ts" />
- [Guide - Performance - Computed Stability](/guide/best-practices/performance#computed-stability)
## reactive() {#reactive}
Returns a reactive proxy of the object.
- **Type**
```ts
function reactive<T extends object>(target: T): UnwrapNestedRefs<T>
```
- **Details**
The reactive conversion is "deep": it affects all nested properties. A reactive object also deeply unwraps any properties that are [refs](#ref) while maintaining reactivity.
It should also be noted that there is no ref unwrapping performed when the ref is accessed as an element of a reactive array or a native collection type like `Map`.
To avoid the deep conversion and only retain reactivity at the root level, use [shallowReactive()](./reactivity-advanced#shallowreactive) instead.
The returned object and its nested objects are wrapped with [ES Proxy](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy) and **not** equal to the original objects. It is recommended to work exclusively with the reactive proxy and avoid relying on the original object.
- **Example**
Creating a reactive object:
```js
const obj = reactive({ count: 0 })
obj.count++
```
Ref unwrapping:
```ts
const count = ref(1)
const obj = reactive({ count })
// ref will be unwrapped
console.log(obj.count === count.value) // true
// it will update `obj.count`
count.value++
console.log(count.value) // 2
console.log(obj.count) // 2
// it will also update `count` ref
obj.count++
console.log(obj.count) // 3
console.log(count.value) // 3
```
Note that refs are **not** unwrapped when accessed as array or collection elements:
```js
const books = reactive([ref('Vue 3 Guide')])
// need .value here
console.log(books[0].value)
const map = reactive(new Map([['count', ref(0)]]))
// need .value here
console.log(map.get('count').value)
```
When assigning a [ref](#ref) to a `reactive` property, that ref will also be automatically unwrapped:
```ts
const count = ref(1)
const obj = reactive({})
obj.count = count
console.log(obj.count) // 1
console.log(obj.count === count.value) // true
```
- **See also**
- [Guide - Reactivity Fundamentals](/guide/essentials/reactivity-fundamentals)
- [Guide - Typing `reactive()`](/guide/typescript/composition-api#typing-reactive) <sup class="vt-badge ts" />
## readonly() {#readonly}
Takes an object (reactive or plain) or a [ref](#ref) and returns a readonly proxy to the original.
- **Type**
```ts
function readonly<T extends object>(
target: T
): DeepReadonly<UnwrapNestedRefs<T>>
```
- **Details**
A readonly proxy is deep: any nested property accessed will be readonly as well. It also has the same ref-unwrapping behavior as `reactive()`, except the unwrapped values will also be made readonly.
To avoid the deep conversion, use [shallowReadonly()](./reactivity-advanced#shallowreadonly) instead.
- **Example**
```js
const original = reactive({ count: 0 })
const copy = readonly(original)
watchEffect(() => {
// works for reactivity tracking
console.log(copy.count)
})
// mutating original will trigger watchers relying on the copy
original.count++
// mutating the copy will fail and result in a warning
copy.count++ // warning!
```
## watchEffect() {#watcheffect}
Runs a function immediately while reactively tracking its dependencies and re-runs it whenever the dependencies are changed.
- **Type**
```ts
function watchEffect(
effect: (onCleanup: OnCleanup) => void,
options?: WatchEffectOptions
): WatchHandle
type OnCleanup = (cleanupFn: () => void) => void
interface WatchEffectOptions {
flush?: 'pre' | 'post' | 'sync' // default: 'pre'
onTrack?: (event: DebuggerEvent) => void
onTrigger?: (event: DebuggerEvent) => void
}
interface WatchHandle {
(): void // callable, same as `stop`
pause: () => void
resume: () => void
stop: () => void
}
```
- **Details**
The first argument is the effect function to be run. The effect function receives a function that can be used to register a cleanup callback. The cleanup callback will be called right before the next time the effect is re-run, and can be used to clean up invalidated side effects, e.g. a pending async request (see example below).
The second argument is an optional options object that can be used to adjust the effect's flush timing or to debug the effect's dependencies.
By default, watchers will run just prior to component rendering. Setting `flush: 'post'` will defer the watcher until after component rendering. See [Callback Flush Timing](/guide/essentials/watchers#callback-flush-timing) for more information. In rare cases, it might be necessary to trigger a watcher immediately when a reactive dependency changes, e.g. to invalidate a cache. This can be achieved using `flush: 'sync'`. However, this setting should be used with caution, as it can lead to problems with performance and data consistency if multiple properties are being updated at the same time.
The return value is a handle function that can be called to stop the effect from running again.
- **Example**
```js
const count = ref(0)
watchEffect(() => console.log(count.value))
// -> logs 0
count.value++
// -> logs 1
```
Stopping the watcher:
```js
const stop = watchEffect(() => {})
// when the watcher is no longer needed:
stop()
```
Pausing / resuming the watcher: <sup class="vt-badge" data-text="3.5+" />
```js
const { stop, pause, resume } = watchEffect(() => {})
// temporarily pause the watcher
pause()
// resume later
resume()
// stop
stop()
```
Side effect cleanup:
```js
watchEffect(async (onCleanup) => {
const { response, cancel } = doAsyncWork(newId)
// `cancel` will be called if `id` changes, cancelling
// the previous request if it hasn't completed yet
onCleanup(cancel)
data.value = await response
})
```
Side effect cleanup in 3.5+:
```js
import { onWatcherCleanup } from 'vue'
watchEffect(async () => {
const { response, cancel } = doAsyncWork(newId)
// `cancel` will be called if `id` changes, cancelling
// the previous request if it hasn't completed yet
onWatcherCleanup(cancel)
data.value = await response
})
```
Options:
```js
watchEffect(() => {}, {
flush: 'post',
onTrack(e) {
debugger
},
onTrigger(e) {
debugger
}
})
```
- **See also**
- [Guide - Watchers](/guide/essentials/watchers#watcheffect)
- [Guide - Watcher Debugging](/guide/extras/reactivity-in-depth#watcher-debugging)
## watchPostEffect() {#watchposteffect}
Alias of [`watchEffect()`](#watcheffect) with `flush: 'post'` option.
## watchSyncEffect() {#watchsynceffect}
Alias of [`watchEffect()`](#watcheffect) with `flush: 'sync'` option.
## watch() {#watch}
Watches one or more reactive data sources and invokes a callback function when the sources change.
- **Type**
```ts
// watching single source
function watch<T>(
source: WatchSource<T>,
callback: WatchCallback<T>,
options?: WatchOptions
): WatchHandle
// watching multiple sources
function watch<T>(
sources: WatchSource<T>[],
callback: WatchCallback<T[]>,
options?: WatchOptions
): WatchHandle
type WatchCallback<T> = (
value: T,
oldValue: T,
onCleanup: (cleanupFn: () => void) => void
) => void
type WatchSource<T> =
| Ref<T> // ref
| (() => T) // getter
| (T extends object ? T : never) // reactive object
interface WatchOptions extends WatchEffectOptions {
immediate?: boolean // default: false
deep?: boolean | number // default: false
flush?: 'pre' | 'post' | 'sync' // default: 'pre'
onTrack?: (event: DebuggerEvent) => void
onTrigger?: (event: DebuggerEvent) => void
once?: boolean // default: false (3.4+)
}
interface WatchHandle {
(): void // callable, same as `stop`
pause: () => void
resume: () => void
stop: () => void
}
```
> Types are simplified for readability.
- **Details**
`watch()` is lazy by default - i.e. the callback is only called when the watched source has changed.
The first argument is the watcher's **source**. The source can be one of the following:
- A getter function that returns a value
- A ref
- A reactive object
- ...or an array of the above.
The second argument is the callback that will be called when the source changes. The callback receives three arguments: the new value, the old value, and a function for registering a side effect cleanup callback. The cleanup callback will be called right before the next time the effect is re-run, and can be used to clean up invalidated side effects, e.g. a pending async request.
When watching multiple sources, the callback receives two arrays containing new / old values corresponding to the source array.
The third optional argument is an options object that supports the following options:
- **`immediate`**: trigger the callback immediately on watcher creation. Old value will be `undefined` on the first call.
- **`deep`**: force deep traversal of the source if it is an object, so that the callback fires on deep mutations. In 3.5+, this can also be a number indicating the max traversal depth. See [Deep Watchers](/guide/essentials/watchers#deep-watchers).
- **`flush`**: adjust the callback's flush timing. See [Callback Flush Timing](/guide/essentials/watchers#callback-flush-timing) and [`watchEffect()`](/api/reactivity-core#watcheffect).
- **`onTrack / onTrigger`**: debug the watcher's dependencies. See [Watcher Debugging](/guide/extras/reactivity-in-depth#watcher-debugging).
- **`once`**: (3.4+) run the callback only once. The watcher is automatically stopped after the first callback run.
Compared to [`watchEffect()`](#watcheffect), `watch()` allows us to:
- Perform the side effect lazily;
- Be more specific about what state should trigger the watcher to re-run;
- Access both the previous and current value of the watched state.
- **Example**
Watching a getter:
```js
const state = reactive({ count: 0 })
watch(
() => state.count,
(count, prevCount) => {
/* ... */
}
)
```
Watching a ref:
```js
const count = ref(0)
watch(count, (count, prevCount) => {
/* ... */
})
```
When watching multiple sources, the callback receives arrays containing new / old values corresponding to the source array:
```js
watch([fooRef, barRef], ([foo, bar], [prevFoo, prevBar]) => {
/* ... */
})
```
When using a getter source, the watcher only fires if the getter's return value has changed. If you want the callback to fire even on deep mutations, you need to explicitly force the watcher into deep mode with `{ deep: true }`. Note in deep mode, the new value and the old will be the same object if the callback was triggered by a deep mutation:
```js
const state = reactive({ count: 0 })
watch(
() => state,
(newValue, oldValue) => {
// newValue === oldValue
},
{ deep: true }
)
```
When directly watching a reactive object, the watcher is automatically in deep mode:
```js
const state = reactive({ count: 0 })
watch(state, () => {
/* triggers on deep mutation to state */
})
```
`watch()` shares the same flush timing and debugging options with [`watchEffect()`](#watcheffect):
```js
watch(source, callback, {
flush: 'post',
onTrack(e) {
debugger
},
onTrigger(e) {
debugger
}
})
```
Stopping the watcher:
```js
const stop = watch(source, callback)
// when the watcher is no longer needed:
stop()
```
Pausing / resuming the watcher: <sup class="vt-badge" data-text="3.5+" />
```js
const { stop, pause, resume } = watch(() => {})
// temporarily pause the watcher
pause()
// resume later
resume()
// stop
stop()
```
Side effect cleanup:
```js
watch(id, async (newId, oldId, onCleanup) => {
const { response, cancel } = doAsyncWork(newId)
// `cancel` will be called if `id` changes, cancelling
// the previous request if it hasn't completed yet
onCleanup(cancel)
data.value = await response
})
```
Side effect cleanup in 3.5+:
```js
import { onWatcherCleanup } from 'vue'
watch(id, async (newId) => {
const { response, cancel } = doAsyncWork(newId)
onWatcherCleanup(cancel)
data.value = await response
})
```
- **See also**
- [Guide - Watchers](/guide/essentials/watchers)
- [Guide - Watcher Debugging](/guide/extras/reactivity-in-depth#watcher-debugging)
## onWatcherCleanup() <sup class="vt-badge" data-text="3.5+" /> {#onwatchercleanup}
Register a cleanup function to be executed when the current watcher is about to re-run. Can only be called during the synchronous execution of a `watchEffect` effect function or `watch` callback function (i.e. it cannot be called after an `await` statement in an async function.)
- **Type**
```ts
function onWatcherCleanup(
cleanupFn: () => void,
failSilently?: boolean
): void
```
- **Example**
```ts
import { watch, onWatcherCleanup } from 'vue'
watch(id, (newId) => {
const { response, cancel } = doAsyncWork(newId)
// `cancel` will be called if `id` changes, cancelling
// the previous request if it hasn't completed yet
onWatcherCleanup(cancel)
})
```