@reactive-vscode/reactivity 0.0.1-beta.5 → 0.0.1-beta.6
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/package.json +1 -1
- package/shim.d.ts +4 -4
- package/src/apiWatch.ts +415 -415
- package/src/errorHandling.ts +119 -119
- package/src/scheduler.ts +210 -210
package/src/apiWatch.ts
CHANGED
|
@@ -1,415 +1,415 @@
|
|
|
1
|
-
import {
|
|
2
|
-
type ComputedRef,
|
|
3
|
-
type DebuggerOptions,
|
|
4
|
-
type EffectScheduler,
|
|
5
|
-
ReactiveEffect,
|
|
6
|
-
ReactiveFlags,
|
|
7
|
-
type Ref,
|
|
8
|
-
getCurrentScope,
|
|
9
|
-
isReactive,
|
|
10
|
-
isRef,
|
|
11
|
-
isShallow,
|
|
12
|
-
} from '@vue/reactivity'
|
|
13
|
-
import { type SchedulerJob, queueJob } from './scheduler'
|
|
14
|
-
import {
|
|
15
|
-
EMPTY_OBJ,
|
|
16
|
-
NOOP,
|
|
17
|
-
extend,
|
|
18
|
-
hasChanged,
|
|
19
|
-
isArray,
|
|
20
|
-
isFunction,
|
|
21
|
-
isMap,
|
|
22
|
-
isObject,
|
|
23
|
-
isPlainObject,
|
|
24
|
-
isSet,
|
|
25
|
-
remove,
|
|
26
|
-
} from '@vue/shared'
|
|
27
|
-
import {
|
|
28
|
-
ErrorCodes,
|
|
29
|
-
callWithAsyncErrorHandling,
|
|
30
|
-
callWithErrorHandling,
|
|
31
|
-
} from './errorHandling'
|
|
32
|
-
import { warn } from './warning'
|
|
33
|
-
|
|
34
|
-
export type WatchEffect = (onCleanup: OnCleanup) => void
|
|
35
|
-
|
|
36
|
-
export type WatchSource<T = any> = Ref<T> | ComputedRef<T> | (() => T)
|
|
37
|
-
|
|
38
|
-
export type WatchCallback<V = any, OV = any> = (
|
|
39
|
-
value: V,
|
|
40
|
-
oldValue: OV,
|
|
41
|
-
onCleanup: OnCleanup,
|
|
42
|
-
) => any
|
|
43
|
-
|
|
44
|
-
type MapSources<T, Immediate> = {
|
|
45
|
-
[K in keyof T]: T[K] extends WatchSource<infer V>
|
|
46
|
-
? Immediate extends true
|
|
47
|
-
? V | undefined
|
|
48
|
-
: V
|
|
49
|
-
: T[K] extends object
|
|
50
|
-
? Immediate extends true
|
|
51
|
-
? T[K] | undefined
|
|
52
|
-
: T[K]
|
|
53
|
-
: never
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
export type OnCleanup = (cleanupFn: () => void) => void
|
|
57
|
-
|
|
58
|
-
export interface WatchOptionsBase extends DebuggerOptions {
|
|
59
|
-
flush?: 'pre' | 'post' | 'sync'
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
export interface WatchOptions<Immediate = boolean> extends WatchOptionsBase {
|
|
63
|
-
immediate?: Immediate
|
|
64
|
-
deep?: boolean
|
|
65
|
-
once?: boolean
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
export type WatchStopHandle = () => void
|
|
69
|
-
|
|
70
|
-
// Simple effect.
|
|
71
|
-
export function watchEffect(
|
|
72
|
-
effect: WatchEffect,
|
|
73
|
-
options?: WatchOptionsBase,
|
|
74
|
-
): WatchStopHandle {
|
|
75
|
-
return doWatch(effect, null, options)
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
export function watchPostEffect(
|
|
79
|
-
effect: WatchEffect,
|
|
80
|
-
options?: DebuggerOptions,
|
|
81
|
-
) {
|
|
82
|
-
return doWatch(
|
|
83
|
-
effect,
|
|
84
|
-
null,
|
|
85
|
-
__DEV__ ? extend({}, options as any, { flush: 'post' }) : { flush: 'post' },
|
|
86
|
-
)
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
export function watchSyncEffect(
|
|
90
|
-
effect: WatchEffect,
|
|
91
|
-
options?: DebuggerOptions,
|
|
92
|
-
) {
|
|
93
|
-
return doWatch(
|
|
94
|
-
effect,
|
|
95
|
-
null,
|
|
96
|
-
__DEV__ ? extend({}, options as any, { flush: 'sync' }) : { flush: 'sync' },
|
|
97
|
-
)
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
// initial value for watchers to trigger on undefined initial values
|
|
101
|
-
const INITIAL_WATCHER_VALUE = {}
|
|
102
|
-
|
|
103
|
-
type MultiWatchSources = (WatchSource<unknown> | object)[]
|
|
104
|
-
|
|
105
|
-
// overload: single source + cb
|
|
106
|
-
export function watch<T, Immediate extends Readonly<boolean> = false>(
|
|
107
|
-
source: WatchSource<T>,
|
|
108
|
-
cb: WatchCallback<T, Immediate extends true ? T | undefined : T>,
|
|
109
|
-
options?: WatchOptions<Immediate>,
|
|
110
|
-
): WatchStopHandle
|
|
111
|
-
|
|
112
|
-
// overload: array of multiple sources + cb
|
|
113
|
-
export function watch<
|
|
114
|
-
T extends MultiWatchSources,
|
|
115
|
-
Immediate extends Readonly<boolean> = false,
|
|
116
|
-
>(
|
|
117
|
-
sources: [...T],
|
|
118
|
-
cb: WatchCallback<MapSources<T, false>, MapSources<T, Immediate>>,
|
|
119
|
-
options?: WatchOptions<Immediate>,
|
|
120
|
-
): WatchStopHandle
|
|
121
|
-
|
|
122
|
-
// overload: multiple sources w/ `as const`
|
|
123
|
-
// watch([foo, bar] as const, () => {})
|
|
124
|
-
// somehow [...T] breaks when the type is readonly
|
|
125
|
-
export function watch<
|
|
126
|
-
T extends Readonly<MultiWatchSources>,
|
|
127
|
-
Immediate extends Readonly<boolean> = false,
|
|
128
|
-
>(
|
|
129
|
-
source: T,
|
|
130
|
-
cb: WatchCallback<MapSources<T, false>, MapSources<T, Immediate>>,
|
|
131
|
-
options?: WatchOptions<Immediate>,
|
|
132
|
-
): WatchStopHandle
|
|
133
|
-
|
|
134
|
-
// overload: watching reactive object w/ cb
|
|
135
|
-
export function watch<
|
|
136
|
-
T extends object,
|
|
137
|
-
Immediate extends Readonly<boolean> = false,
|
|
138
|
-
>(
|
|
139
|
-
source: T,
|
|
140
|
-
cb: WatchCallback<T, Immediate extends true ? T | undefined : T>,
|
|
141
|
-
options?: WatchOptions<Immediate>,
|
|
142
|
-
): WatchStopHandle
|
|
143
|
-
|
|
144
|
-
// implementation
|
|
145
|
-
export function watch<T = any, Immediate extends Readonly<boolean> = false>(
|
|
146
|
-
source: T | WatchSource<T>,
|
|
147
|
-
cb: any,
|
|
148
|
-
options?: WatchOptions<Immediate>,
|
|
149
|
-
): WatchStopHandle {
|
|
150
|
-
if (__DEV__ && !isFunction(cb)) {
|
|
151
|
-
warn(
|
|
152
|
-
`\`watch(fn, options?)\` signature has been moved to a separate API. ` +
|
|
153
|
-
`Use \`watchEffect(fn, options?)\` instead. \`watch\` now only ` +
|
|
154
|
-
`supports \`watch(source, cb, options?) signature.`,
|
|
155
|
-
)
|
|
156
|
-
}
|
|
157
|
-
return doWatch(source as any, cb, options)
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
function doWatch(
|
|
161
|
-
source: WatchSource | WatchSource[] | WatchEffect | object,
|
|
162
|
-
cb: WatchCallback | null,
|
|
163
|
-
{
|
|
164
|
-
immediate,
|
|
165
|
-
deep,
|
|
166
|
-
flush,
|
|
167
|
-
once,
|
|
168
|
-
onTrack,
|
|
169
|
-
onTrigger,
|
|
170
|
-
}: WatchOptions = EMPTY_OBJ,
|
|
171
|
-
): WatchStopHandle {
|
|
172
|
-
if (cb && once) {
|
|
173
|
-
const _cb = cb
|
|
174
|
-
cb = (...args) => {
|
|
175
|
-
_cb(...args)
|
|
176
|
-
unwatch()
|
|
177
|
-
}
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
// TODO remove in 3.5
|
|
181
|
-
if (__DEV__ && deep !== void 0 && typeof deep === 'number') {
|
|
182
|
-
warn(
|
|
183
|
-
`watch() "deep" option with number value will be used as watch depth in future versions. ` +
|
|
184
|
-
`Please use a boolean instead to avoid potential breakage.`,
|
|
185
|
-
)
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
if (__DEV__ && !cb) {
|
|
189
|
-
if (immediate !== undefined) {
|
|
190
|
-
warn(
|
|
191
|
-
`watch() "immediate" option is only respected when using the ` +
|
|
192
|
-
`watch(source, callback, options?) signature.`,
|
|
193
|
-
)
|
|
194
|
-
}
|
|
195
|
-
if (deep !== undefined) {
|
|
196
|
-
warn(
|
|
197
|
-
`watch() "deep" option is only respected when using the ` +
|
|
198
|
-
`watch(source, callback, options?) signature.`,
|
|
199
|
-
)
|
|
200
|
-
}
|
|
201
|
-
if (once !== undefined) {
|
|
202
|
-
warn(
|
|
203
|
-
`watch() "once" option is only respected when using the ` +
|
|
204
|
-
`watch(source, callback, options?) signature.`,
|
|
205
|
-
)
|
|
206
|
-
}
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
const warnInvalidSource = (s: unknown) => {
|
|
210
|
-
warn(
|
|
211
|
-
`Invalid watch source: `,
|
|
212
|
-
s,
|
|
213
|
-
`A watch source can only be a getter/effect function, a ref, ` +
|
|
214
|
-
`a reactive object, or an array of these types.`,
|
|
215
|
-
)
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
const instance = null
|
|
219
|
-
const reactiveGetter = (source: object) =>
|
|
220
|
-
deep === true
|
|
221
|
-
? source // traverse will happen in wrapped getter below
|
|
222
|
-
: // for deep: false, only traverse root-level properties
|
|
223
|
-
traverse(source, deep === false ? 1 : undefined)
|
|
224
|
-
|
|
225
|
-
let getter: () => any
|
|
226
|
-
let forceTrigger = false
|
|
227
|
-
let isMultiSource = false
|
|
228
|
-
|
|
229
|
-
if (isRef(source)) {
|
|
230
|
-
getter = () => source.value
|
|
231
|
-
forceTrigger = isShallow(source)
|
|
232
|
-
} else if (isReactive(source)) {
|
|
233
|
-
getter = () => reactiveGetter(source)
|
|
234
|
-
forceTrigger = true
|
|
235
|
-
} else if (isArray(source)) {
|
|
236
|
-
isMultiSource = true
|
|
237
|
-
forceTrigger = source.some(s => isReactive(s) || isShallow(s))
|
|
238
|
-
getter = () =>
|
|
239
|
-
source.map(s => {
|
|
240
|
-
if (isRef(s)) {
|
|
241
|
-
return s.value
|
|
242
|
-
} else if (isReactive(s)) {
|
|
243
|
-
return reactiveGetter(s)
|
|
244
|
-
} else if (isFunction(s)) {
|
|
245
|
-
return callWithErrorHandling(s, instance, ErrorCodes.WATCH_GETTER)
|
|
246
|
-
} else {
|
|
247
|
-
__DEV__ && warnInvalidSource(s)
|
|
248
|
-
}
|
|
249
|
-
})
|
|
250
|
-
} else if (isFunction(source)) {
|
|
251
|
-
if (cb) {
|
|
252
|
-
// getter with cb
|
|
253
|
-
getter = () =>
|
|
254
|
-
callWithErrorHandling(source, instance, ErrorCodes.WATCH_GETTER)
|
|
255
|
-
} else {
|
|
256
|
-
// no cb -> simple effect
|
|
257
|
-
getter = () => {
|
|
258
|
-
if (cleanup) {
|
|
259
|
-
cleanup()
|
|
260
|
-
}
|
|
261
|
-
return callWithAsyncErrorHandling(
|
|
262
|
-
source,
|
|
263
|
-
instance,
|
|
264
|
-
ErrorCodes.WATCH_CALLBACK,
|
|
265
|
-
[onCleanup],
|
|
266
|
-
)
|
|
267
|
-
}
|
|
268
|
-
}
|
|
269
|
-
} else {
|
|
270
|
-
getter = NOOP
|
|
271
|
-
__DEV__ && warnInvalidSource(source)
|
|
272
|
-
}
|
|
273
|
-
|
|
274
|
-
if (cb && deep) {
|
|
275
|
-
const baseGetter = getter
|
|
276
|
-
getter = () => traverse(baseGetter())
|
|
277
|
-
}
|
|
278
|
-
|
|
279
|
-
let cleanup: (() => void) | undefined
|
|
280
|
-
let onCleanup: OnCleanup = (fn: () => void) => {
|
|
281
|
-
cleanup = effect.onStop = () => {
|
|
282
|
-
callWithErrorHandling(fn, instance, ErrorCodes.WATCH_CLEANUP)
|
|
283
|
-
cleanup = effect.onStop = undefined
|
|
284
|
-
}
|
|
285
|
-
}
|
|
286
|
-
|
|
287
|
-
let oldValue: any = isMultiSource
|
|
288
|
-
? new Array((source as []).length).fill(INITIAL_WATCHER_VALUE)
|
|
289
|
-
: INITIAL_WATCHER_VALUE
|
|
290
|
-
const job: SchedulerJob = () => {
|
|
291
|
-
if (!effect.active || !effect.dirty) {
|
|
292
|
-
return
|
|
293
|
-
}
|
|
294
|
-
if (cb) {
|
|
295
|
-
// watch(source, cb)
|
|
296
|
-
const newValue = effect.run()
|
|
297
|
-
if (
|
|
298
|
-
deep ||
|
|
299
|
-
forceTrigger ||
|
|
300
|
-
(isMultiSource
|
|
301
|
-
? (newValue as any[]).some((v, i) => hasChanged(v, oldValue[i]))
|
|
302
|
-
: hasChanged(newValue, oldValue))
|
|
303
|
-
) {
|
|
304
|
-
// cleanup before running cb again
|
|
305
|
-
if (cleanup) {
|
|
306
|
-
cleanup()
|
|
307
|
-
}
|
|
308
|
-
callWithAsyncErrorHandling(cb, instance, ErrorCodes.WATCH_CALLBACK, [
|
|
309
|
-
newValue,
|
|
310
|
-
// pass undefined as the old value when it's changed for the first time
|
|
311
|
-
oldValue === INITIAL_WATCHER_VALUE
|
|
312
|
-
? undefined
|
|
313
|
-
: isMultiSource && oldValue[0] === INITIAL_WATCHER_VALUE
|
|
314
|
-
? []
|
|
315
|
-
: oldValue,
|
|
316
|
-
onCleanup,
|
|
317
|
-
])
|
|
318
|
-
oldValue = newValue
|
|
319
|
-
}
|
|
320
|
-
} else {
|
|
321
|
-
// watchEffect
|
|
322
|
-
effect.run()
|
|
323
|
-
}
|
|
324
|
-
}
|
|
325
|
-
|
|
326
|
-
// important: mark the job as a watcher callback so that scheduler knows
|
|
327
|
-
// it is allowed to self-trigger (#1727)
|
|
328
|
-
job.allowRecurse = !!cb
|
|
329
|
-
|
|
330
|
-
let scheduler: EffectScheduler
|
|
331
|
-
if (flush === 'sync') {
|
|
332
|
-
scheduler = job as any // the scheduler function gets called directly
|
|
333
|
-
} else {
|
|
334
|
-
// default: 'pre'
|
|
335
|
-
job.pre = true
|
|
336
|
-
scheduler = () => queueJob(job)
|
|
337
|
-
}
|
|
338
|
-
|
|
339
|
-
const effect = new ReactiveEffect(getter, NOOP, scheduler)
|
|
340
|
-
|
|
341
|
-
const scope = getCurrentScope()
|
|
342
|
-
const unwatch = () => {
|
|
343
|
-
effect.stop()
|
|
344
|
-
if (scope) {
|
|
345
|
-
// @ts-ignore for internal use
|
|
346
|
-
remove(scope.effects, effect)
|
|
347
|
-
}
|
|
348
|
-
}
|
|
349
|
-
|
|
350
|
-
if (__DEV__) {
|
|
351
|
-
effect.onTrack = onTrack
|
|
352
|
-
effect.onTrigger = onTrigger
|
|
353
|
-
}
|
|
354
|
-
|
|
355
|
-
// initial run
|
|
356
|
-
if (cb) {
|
|
357
|
-
if (immediate) {
|
|
358
|
-
job()
|
|
359
|
-
} else {
|
|
360
|
-
oldValue = effect.run()
|
|
361
|
-
}
|
|
362
|
-
} else {
|
|
363
|
-
effect.run()
|
|
364
|
-
}
|
|
365
|
-
return unwatch
|
|
366
|
-
}
|
|
367
|
-
|
|
368
|
-
export function createPathGetter(ctx: any, path: string) {
|
|
369
|
-
const segments = path.split('.')
|
|
370
|
-
return () => {
|
|
371
|
-
let cur = ctx
|
|
372
|
-
for (let i = 0; i < segments.length && cur; i++) {
|
|
373
|
-
cur = cur[segments[i]]
|
|
374
|
-
}
|
|
375
|
-
return cur
|
|
376
|
-
}
|
|
377
|
-
}
|
|
378
|
-
|
|
379
|
-
export function traverse(
|
|
380
|
-
value: unknown,
|
|
381
|
-
depth = Infinity,
|
|
382
|
-
seen?: Set<unknown>,
|
|
383
|
-
) {
|
|
384
|
-
if (depth <= 0 || !isObject(value) || (value as any)[ReactiveFlags.SKIP]) {
|
|
385
|
-
return value
|
|
386
|
-
}
|
|
387
|
-
|
|
388
|
-
seen = seen || new Set()
|
|
389
|
-
if (seen.has(value)) {
|
|
390
|
-
return value
|
|
391
|
-
}
|
|
392
|
-
seen.add(value)
|
|
393
|
-
depth--
|
|
394
|
-
if (isRef(value)) {
|
|
395
|
-
traverse(value.value, depth, seen)
|
|
396
|
-
} else if (isArray(value)) {
|
|
397
|
-
for (let i = 0; i < value.length; i++) {
|
|
398
|
-
traverse(value[i], depth, seen)
|
|
399
|
-
}
|
|
400
|
-
} else if (isSet(value) || isMap(value)) {
|
|
401
|
-
value.forEach((v: any) => {
|
|
402
|
-
traverse(v, depth, seen)
|
|
403
|
-
})
|
|
404
|
-
} else if (isPlainObject(value)) {
|
|
405
|
-
for (const key in value) {
|
|
406
|
-
traverse(value[key], depth, seen)
|
|
407
|
-
}
|
|
408
|
-
for (const key of Object.getOwnPropertySymbols(value)) {
|
|
409
|
-
if (Object.prototype.propertyIsEnumerable.call(value, key)) {
|
|
410
|
-
traverse(value[key as any], depth, seen)
|
|
411
|
-
}
|
|
412
|
-
}
|
|
413
|
-
}
|
|
414
|
-
return value
|
|
415
|
-
}
|
|
1
|
+
import {
|
|
2
|
+
type ComputedRef,
|
|
3
|
+
type DebuggerOptions,
|
|
4
|
+
type EffectScheduler,
|
|
5
|
+
ReactiveEffect,
|
|
6
|
+
ReactiveFlags,
|
|
7
|
+
type Ref,
|
|
8
|
+
getCurrentScope,
|
|
9
|
+
isReactive,
|
|
10
|
+
isRef,
|
|
11
|
+
isShallow,
|
|
12
|
+
} from '@vue/reactivity'
|
|
13
|
+
import { type SchedulerJob, queueJob } from './scheduler'
|
|
14
|
+
import {
|
|
15
|
+
EMPTY_OBJ,
|
|
16
|
+
NOOP,
|
|
17
|
+
extend,
|
|
18
|
+
hasChanged,
|
|
19
|
+
isArray,
|
|
20
|
+
isFunction,
|
|
21
|
+
isMap,
|
|
22
|
+
isObject,
|
|
23
|
+
isPlainObject,
|
|
24
|
+
isSet,
|
|
25
|
+
remove,
|
|
26
|
+
} from '@vue/shared'
|
|
27
|
+
import {
|
|
28
|
+
ErrorCodes,
|
|
29
|
+
callWithAsyncErrorHandling,
|
|
30
|
+
callWithErrorHandling,
|
|
31
|
+
} from './errorHandling'
|
|
32
|
+
import { warn } from './warning'
|
|
33
|
+
|
|
34
|
+
export type WatchEffect = (onCleanup: OnCleanup) => void
|
|
35
|
+
|
|
36
|
+
export type WatchSource<T = any> = Ref<T> | ComputedRef<T> | (() => T)
|
|
37
|
+
|
|
38
|
+
export type WatchCallback<V = any, OV = any> = (
|
|
39
|
+
value: V,
|
|
40
|
+
oldValue: OV,
|
|
41
|
+
onCleanup: OnCleanup,
|
|
42
|
+
) => any
|
|
43
|
+
|
|
44
|
+
type MapSources<T, Immediate> = {
|
|
45
|
+
[K in keyof T]: T[K] extends WatchSource<infer V>
|
|
46
|
+
? Immediate extends true
|
|
47
|
+
? V | undefined
|
|
48
|
+
: V
|
|
49
|
+
: T[K] extends object
|
|
50
|
+
? Immediate extends true
|
|
51
|
+
? T[K] | undefined
|
|
52
|
+
: T[K]
|
|
53
|
+
: never
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export type OnCleanup = (cleanupFn: () => void) => void
|
|
57
|
+
|
|
58
|
+
export interface WatchOptionsBase extends DebuggerOptions {
|
|
59
|
+
flush?: 'pre' | 'post' | 'sync'
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export interface WatchOptions<Immediate = boolean> extends WatchOptionsBase {
|
|
63
|
+
immediate?: Immediate
|
|
64
|
+
deep?: boolean
|
|
65
|
+
once?: boolean
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export type WatchStopHandle = () => void
|
|
69
|
+
|
|
70
|
+
// Simple effect.
|
|
71
|
+
export function watchEffect(
|
|
72
|
+
effect: WatchEffect,
|
|
73
|
+
options?: WatchOptionsBase,
|
|
74
|
+
): WatchStopHandle {
|
|
75
|
+
return doWatch(effect, null, options)
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export function watchPostEffect(
|
|
79
|
+
effect: WatchEffect,
|
|
80
|
+
options?: DebuggerOptions,
|
|
81
|
+
) {
|
|
82
|
+
return doWatch(
|
|
83
|
+
effect,
|
|
84
|
+
null,
|
|
85
|
+
__DEV__ ? extend({}, options as any, { flush: 'post' }) : { flush: 'post' },
|
|
86
|
+
)
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export function watchSyncEffect(
|
|
90
|
+
effect: WatchEffect,
|
|
91
|
+
options?: DebuggerOptions,
|
|
92
|
+
) {
|
|
93
|
+
return doWatch(
|
|
94
|
+
effect,
|
|
95
|
+
null,
|
|
96
|
+
__DEV__ ? extend({}, options as any, { flush: 'sync' }) : { flush: 'sync' },
|
|
97
|
+
)
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// initial value for watchers to trigger on undefined initial values
|
|
101
|
+
const INITIAL_WATCHER_VALUE = {}
|
|
102
|
+
|
|
103
|
+
type MultiWatchSources = (WatchSource<unknown> | object)[]
|
|
104
|
+
|
|
105
|
+
// overload: single source + cb
|
|
106
|
+
export function watch<T, Immediate extends Readonly<boolean> = false>(
|
|
107
|
+
source: WatchSource<T>,
|
|
108
|
+
cb: WatchCallback<T, Immediate extends true ? T | undefined : T>,
|
|
109
|
+
options?: WatchOptions<Immediate>,
|
|
110
|
+
): WatchStopHandle
|
|
111
|
+
|
|
112
|
+
// overload: array of multiple sources + cb
|
|
113
|
+
export function watch<
|
|
114
|
+
T extends MultiWatchSources,
|
|
115
|
+
Immediate extends Readonly<boolean> = false,
|
|
116
|
+
>(
|
|
117
|
+
sources: [...T],
|
|
118
|
+
cb: WatchCallback<MapSources<T, false>, MapSources<T, Immediate>>,
|
|
119
|
+
options?: WatchOptions<Immediate>,
|
|
120
|
+
): WatchStopHandle
|
|
121
|
+
|
|
122
|
+
// overload: multiple sources w/ `as const`
|
|
123
|
+
// watch([foo, bar] as const, () => {})
|
|
124
|
+
// somehow [...T] breaks when the type is readonly
|
|
125
|
+
export function watch<
|
|
126
|
+
T extends Readonly<MultiWatchSources>,
|
|
127
|
+
Immediate extends Readonly<boolean> = false,
|
|
128
|
+
>(
|
|
129
|
+
source: T,
|
|
130
|
+
cb: WatchCallback<MapSources<T, false>, MapSources<T, Immediate>>,
|
|
131
|
+
options?: WatchOptions<Immediate>,
|
|
132
|
+
): WatchStopHandle
|
|
133
|
+
|
|
134
|
+
// overload: watching reactive object w/ cb
|
|
135
|
+
export function watch<
|
|
136
|
+
T extends object,
|
|
137
|
+
Immediate extends Readonly<boolean> = false,
|
|
138
|
+
>(
|
|
139
|
+
source: T,
|
|
140
|
+
cb: WatchCallback<T, Immediate extends true ? T | undefined : T>,
|
|
141
|
+
options?: WatchOptions<Immediate>,
|
|
142
|
+
): WatchStopHandle
|
|
143
|
+
|
|
144
|
+
// implementation
|
|
145
|
+
export function watch<T = any, Immediate extends Readonly<boolean> = false>(
|
|
146
|
+
source: T | WatchSource<T>,
|
|
147
|
+
cb: any,
|
|
148
|
+
options?: WatchOptions<Immediate>,
|
|
149
|
+
): WatchStopHandle {
|
|
150
|
+
if (__DEV__ && !isFunction(cb)) {
|
|
151
|
+
warn(
|
|
152
|
+
`\`watch(fn, options?)\` signature has been moved to a separate API. ` +
|
|
153
|
+
`Use \`watchEffect(fn, options?)\` instead. \`watch\` now only ` +
|
|
154
|
+
`supports \`watch(source, cb, options?) signature.`,
|
|
155
|
+
)
|
|
156
|
+
}
|
|
157
|
+
return doWatch(source as any, cb, options)
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
function doWatch(
|
|
161
|
+
source: WatchSource | WatchSource[] | WatchEffect | object,
|
|
162
|
+
cb: WatchCallback | null,
|
|
163
|
+
{
|
|
164
|
+
immediate,
|
|
165
|
+
deep,
|
|
166
|
+
flush,
|
|
167
|
+
once,
|
|
168
|
+
onTrack,
|
|
169
|
+
onTrigger,
|
|
170
|
+
}: WatchOptions = EMPTY_OBJ,
|
|
171
|
+
): WatchStopHandle {
|
|
172
|
+
if (cb && once) {
|
|
173
|
+
const _cb = cb
|
|
174
|
+
cb = (...args) => {
|
|
175
|
+
_cb(...args)
|
|
176
|
+
unwatch()
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// TODO remove in 3.5
|
|
181
|
+
if (__DEV__ && deep !== void 0 && typeof deep === 'number') {
|
|
182
|
+
warn(
|
|
183
|
+
`watch() "deep" option with number value will be used as watch depth in future versions. ` +
|
|
184
|
+
`Please use a boolean instead to avoid potential breakage.`,
|
|
185
|
+
)
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
if (__DEV__ && !cb) {
|
|
189
|
+
if (immediate !== undefined) {
|
|
190
|
+
warn(
|
|
191
|
+
`watch() "immediate" option is only respected when using the ` +
|
|
192
|
+
`watch(source, callback, options?) signature.`,
|
|
193
|
+
)
|
|
194
|
+
}
|
|
195
|
+
if (deep !== undefined) {
|
|
196
|
+
warn(
|
|
197
|
+
`watch() "deep" option is only respected when using the ` +
|
|
198
|
+
`watch(source, callback, options?) signature.`,
|
|
199
|
+
)
|
|
200
|
+
}
|
|
201
|
+
if (once !== undefined) {
|
|
202
|
+
warn(
|
|
203
|
+
`watch() "once" option is only respected when using the ` +
|
|
204
|
+
`watch(source, callback, options?) signature.`,
|
|
205
|
+
)
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
const warnInvalidSource = (s: unknown) => {
|
|
210
|
+
warn(
|
|
211
|
+
`Invalid watch source: `,
|
|
212
|
+
s,
|
|
213
|
+
`A watch source can only be a getter/effect function, a ref, ` +
|
|
214
|
+
`a reactive object, or an array of these types.`,
|
|
215
|
+
)
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
const instance = null
|
|
219
|
+
const reactiveGetter = (source: object) =>
|
|
220
|
+
deep === true
|
|
221
|
+
? source // traverse will happen in wrapped getter below
|
|
222
|
+
: // for deep: false, only traverse root-level properties
|
|
223
|
+
traverse(source, deep === false ? 1 : undefined)
|
|
224
|
+
|
|
225
|
+
let getter: () => any
|
|
226
|
+
let forceTrigger = false
|
|
227
|
+
let isMultiSource = false
|
|
228
|
+
|
|
229
|
+
if (isRef(source)) {
|
|
230
|
+
getter = () => source.value
|
|
231
|
+
forceTrigger = isShallow(source)
|
|
232
|
+
} else if (isReactive(source)) {
|
|
233
|
+
getter = () => reactiveGetter(source)
|
|
234
|
+
forceTrigger = true
|
|
235
|
+
} else if (isArray(source)) {
|
|
236
|
+
isMultiSource = true
|
|
237
|
+
forceTrigger = source.some(s => isReactive(s) || isShallow(s))
|
|
238
|
+
getter = () =>
|
|
239
|
+
source.map(s => {
|
|
240
|
+
if (isRef(s)) {
|
|
241
|
+
return s.value
|
|
242
|
+
} else if (isReactive(s)) {
|
|
243
|
+
return reactiveGetter(s)
|
|
244
|
+
} else if (isFunction(s)) {
|
|
245
|
+
return callWithErrorHandling(s, instance, ErrorCodes.WATCH_GETTER)
|
|
246
|
+
} else {
|
|
247
|
+
__DEV__ && warnInvalidSource(s)
|
|
248
|
+
}
|
|
249
|
+
})
|
|
250
|
+
} else if (isFunction(source)) {
|
|
251
|
+
if (cb) {
|
|
252
|
+
// getter with cb
|
|
253
|
+
getter = () =>
|
|
254
|
+
callWithErrorHandling(source, instance, ErrorCodes.WATCH_GETTER)
|
|
255
|
+
} else {
|
|
256
|
+
// no cb -> simple effect
|
|
257
|
+
getter = () => {
|
|
258
|
+
if (cleanup) {
|
|
259
|
+
cleanup()
|
|
260
|
+
}
|
|
261
|
+
return callWithAsyncErrorHandling(
|
|
262
|
+
source,
|
|
263
|
+
instance,
|
|
264
|
+
ErrorCodes.WATCH_CALLBACK,
|
|
265
|
+
[onCleanup],
|
|
266
|
+
)
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
} else {
|
|
270
|
+
getter = NOOP
|
|
271
|
+
__DEV__ && warnInvalidSource(source)
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
if (cb && deep) {
|
|
275
|
+
const baseGetter = getter
|
|
276
|
+
getter = () => traverse(baseGetter())
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
let cleanup: (() => void) | undefined
|
|
280
|
+
let onCleanup: OnCleanup = (fn: () => void) => {
|
|
281
|
+
cleanup = effect.onStop = () => {
|
|
282
|
+
callWithErrorHandling(fn, instance, ErrorCodes.WATCH_CLEANUP)
|
|
283
|
+
cleanup = effect.onStop = undefined
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
let oldValue: any = isMultiSource
|
|
288
|
+
? new Array((source as []).length).fill(INITIAL_WATCHER_VALUE)
|
|
289
|
+
: INITIAL_WATCHER_VALUE
|
|
290
|
+
const job: SchedulerJob = () => {
|
|
291
|
+
if (!effect.active || !effect.dirty) {
|
|
292
|
+
return
|
|
293
|
+
}
|
|
294
|
+
if (cb) {
|
|
295
|
+
// watch(source, cb)
|
|
296
|
+
const newValue = effect.run()
|
|
297
|
+
if (
|
|
298
|
+
deep ||
|
|
299
|
+
forceTrigger ||
|
|
300
|
+
(isMultiSource
|
|
301
|
+
? (newValue as any[]).some((v, i) => hasChanged(v, oldValue[i]))
|
|
302
|
+
: hasChanged(newValue, oldValue))
|
|
303
|
+
) {
|
|
304
|
+
// cleanup before running cb again
|
|
305
|
+
if (cleanup) {
|
|
306
|
+
cleanup()
|
|
307
|
+
}
|
|
308
|
+
callWithAsyncErrorHandling(cb, instance, ErrorCodes.WATCH_CALLBACK, [
|
|
309
|
+
newValue,
|
|
310
|
+
// pass undefined as the old value when it's changed for the first time
|
|
311
|
+
oldValue === INITIAL_WATCHER_VALUE
|
|
312
|
+
? undefined
|
|
313
|
+
: isMultiSource && oldValue[0] === INITIAL_WATCHER_VALUE
|
|
314
|
+
? []
|
|
315
|
+
: oldValue,
|
|
316
|
+
onCleanup,
|
|
317
|
+
])
|
|
318
|
+
oldValue = newValue
|
|
319
|
+
}
|
|
320
|
+
} else {
|
|
321
|
+
// watchEffect
|
|
322
|
+
effect.run()
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
// important: mark the job as a watcher callback so that scheduler knows
|
|
327
|
+
// it is allowed to self-trigger (#1727)
|
|
328
|
+
job.allowRecurse = !!cb
|
|
329
|
+
|
|
330
|
+
let scheduler: EffectScheduler
|
|
331
|
+
if (flush === 'sync') {
|
|
332
|
+
scheduler = job as any // the scheduler function gets called directly
|
|
333
|
+
} else {
|
|
334
|
+
// default: 'pre'
|
|
335
|
+
job.pre = true
|
|
336
|
+
scheduler = () => queueJob(job)
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
const effect = new ReactiveEffect(getter, NOOP, scheduler)
|
|
340
|
+
|
|
341
|
+
const scope = getCurrentScope()
|
|
342
|
+
const unwatch = () => {
|
|
343
|
+
effect.stop()
|
|
344
|
+
if (scope) {
|
|
345
|
+
// @ts-ignore for internal use
|
|
346
|
+
remove(scope.effects, effect)
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
if (__DEV__) {
|
|
351
|
+
effect.onTrack = onTrack
|
|
352
|
+
effect.onTrigger = onTrigger
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
// initial run
|
|
356
|
+
if (cb) {
|
|
357
|
+
if (immediate) {
|
|
358
|
+
job()
|
|
359
|
+
} else {
|
|
360
|
+
oldValue = effect.run()
|
|
361
|
+
}
|
|
362
|
+
} else {
|
|
363
|
+
effect.run()
|
|
364
|
+
}
|
|
365
|
+
return unwatch
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
export function createPathGetter(ctx: any, path: string) {
|
|
369
|
+
const segments = path.split('.')
|
|
370
|
+
return () => {
|
|
371
|
+
let cur = ctx
|
|
372
|
+
for (let i = 0; i < segments.length && cur; i++) {
|
|
373
|
+
cur = cur[segments[i]]
|
|
374
|
+
}
|
|
375
|
+
return cur
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
export function traverse(
|
|
380
|
+
value: unknown,
|
|
381
|
+
depth = Infinity,
|
|
382
|
+
seen?: Set<unknown>,
|
|
383
|
+
) {
|
|
384
|
+
if (depth <= 0 || !isObject(value) || (value as any)[ReactiveFlags.SKIP]) {
|
|
385
|
+
return value
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
seen = seen || new Set()
|
|
389
|
+
if (seen.has(value)) {
|
|
390
|
+
return value
|
|
391
|
+
}
|
|
392
|
+
seen.add(value)
|
|
393
|
+
depth--
|
|
394
|
+
if (isRef(value)) {
|
|
395
|
+
traverse(value.value, depth, seen)
|
|
396
|
+
} else if (isArray(value)) {
|
|
397
|
+
for (let i = 0; i < value.length; i++) {
|
|
398
|
+
traverse(value[i], depth, seen)
|
|
399
|
+
}
|
|
400
|
+
} else if (isSet(value) || isMap(value)) {
|
|
401
|
+
value.forEach((v: any) => {
|
|
402
|
+
traverse(v, depth, seen)
|
|
403
|
+
})
|
|
404
|
+
} else if (isPlainObject(value)) {
|
|
405
|
+
for (const key in value) {
|
|
406
|
+
traverse(value[key], depth, seen)
|
|
407
|
+
}
|
|
408
|
+
for (const key of Object.getOwnPropertySymbols(value)) {
|
|
409
|
+
if (Object.prototype.propertyIsEnumerable.call(value, key)) {
|
|
410
|
+
traverse(value[key as any], depth, seen)
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
return value
|
|
415
|
+
}
|