2019-05-29 23:44:59 +08:00
|
|
|
import {
|
2024-08-20 08:21:44 +08:00
|
|
|
type WatchOptions as BaseWatchOptions,
|
2023-01-09 22:20:21 +08:00
|
|
|
type DebuggerOptions,
|
2024-06-10 16:07:25 +08:00
|
|
|
type ReactiveMarker,
|
2024-08-20 16:15:08 +08:00
|
|
|
type WatchCallback,
|
|
|
|
type WatchEffect,
|
2024-08-20 08:21:44 +08:00
|
|
|
type WatchHandle,
|
2024-08-20 16:15:08 +08:00
|
|
|
type WatchSource,
|
2024-08-20 08:21:44 +08:00
|
|
|
watch as baseWatch,
|
2025-05-26 04:43:37 +08:00
|
|
|
traverse,
|
2019-06-11 23:50:28 +08:00
|
|
|
} from '@vue/reactivity'
|
2024-02-26 10:22:12 +08:00
|
|
|
import { type SchedulerJob, SchedulerJobFlags, queueJob } from './scheduler'
|
2025-05-26 04:43:37 +08:00
|
|
|
import {
|
|
|
|
EMPTY_OBJ,
|
|
|
|
NOOP,
|
|
|
|
extend,
|
|
|
|
isArray,
|
|
|
|
isFunction,
|
|
|
|
isString,
|
|
|
|
} from '@vue/shared'
|
2019-09-11 22:09:00 +08:00
|
|
|
import {
|
2019-09-07 00:58:31 +08:00
|
|
|
type ComponentInternalInstance,
|
2019-09-11 22:09:00 +08:00
|
|
|
currentInstance,
|
2021-07-21 02:32:17 +08:00
|
|
|
isInSSRComponentSetup,
|
|
|
|
setCurrentInstance,
|
2019-09-11 22:09:00 +08:00
|
|
|
} from './component'
|
2024-08-20 08:21:44 +08:00
|
|
|
import { callWithAsyncErrorHandling } from './errorHandling'
|
2019-11-03 00:18:35 +08:00
|
|
|
import { queuePostRenderEffect } from './renderer'
|
2019-12-19 00:54:12 +08:00
|
|
|
import { warn } from './warning'
|
2025-05-26 04:43:37 +08:00
|
|
|
import {
|
|
|
|
DeprecationTypes,
|
|
|
|
checkCompatEnabled,
|
|
|
|
isCompatEnabled,
|
|
|
|
} from './compat/compatConfig'
|
2021-04-22 21:49:25 +08:00
|
|
|
import type { ObjectWatchOptionItem } from './componentOptions'
|
2023-12-30 08:35:56 +08:00
|
|
|
import { useSSRContext } from './helpers/useSsrContext'
|
2019-10-22 23:26:48 +08:00
|
|
|
|
2024-08-20 16:15:08 +08:00
|
|
|
export type {
|
|
|
|
WatchHandle,
|
|
|
|
WatchStopHandle,
|
|
|
|
WatchEffect,
|
|
|
|
WatchSource,
|
|
|
|
WatchCallback,
|
|
|
|
OnCleanup,
|
|
|
|
} from '@vue/reactivity'
|
2019-05-29 23:44:59 +08:00
|
|
|
|
2024-06-10 16:07:25 +08:00
|
|
|
type MaybeUndefined<T, I> = I extends true ? T | undefined : T
|
|
|
|
|
2020-10-14 03:45:17 +08:00
|
|
|
type MapSources<T, Immediate> = {
|
2020-02-14 01:08:21 +08:00
|
|
|
[K in keyof T]: T[K] extends WatchSource<infer V>
|
2024-06-10 16:07:25 +08:00
|
|
|
? MaybeUndefined<V, Immediate>
|
2020-05-18 23:02:51 +08:00
|
|
|
: T[K] extends object
|
2024-06-10 16:07:25 +08:00
|
|
|
? MaybeUndefined<T[K], Immediate>
|
2023-11-18 10:33:24 +08:00
|
|
|
: never
|
2020-02-14 01:08:21 +08:00
|
|
|
}
|
|
|
|
|
2024-08-20 16:15:08 +08:00
|
|
|
export interface WatchEffectOptions extends DebuggerOptions {
|
2019-05-29 23:44:59 +08:00
|
|
|
flush?: 'pre' | 'post' | 'sync'
|
|
|
|
}
|
|
|
|
|
2024-08-20 16:15:08 +08:00
|
|
|
export interface WatchOptions<Immediate = boolean> extends WatchEffectOptions {
|
2020-02-18 12:14:19 +08:00
|
|
|
immediate?: Immediate
|
2024-08-02 11:38:07 +08:00
|
|
|
deep?: boolean | number
|
2023-08-30 15:25:51 +08:00
|
|
|
once?: boolean
|
2020-02-18 12:14:19 +08:00
|
|
|
}
|
|
|
|
|
2020-02-22 15:19:10 +08:00
|
|
|
// Simple effect.
|
|
|
|
export function watchEffect(
|
|
|
|
effect: WatchEffect,
|
2024-08-20 16:15:08 +08:00
|
|
|
options?: WatchEffectOptions,
|
2024-08-02 14:41:27 +08:00
|
|
|
): WatchHandle {
|
2020-02-22 15:19:10 +08:00
|
|
|
return doWatch(effect, null, options)
|
|
|
|
}
|
|
|
|
|
2021-07-10 09:47:15 +08:00
|
|
|
export function watchPostEffect(
|
|
|
|
effect: WatchEffect,
|
|
|
|
options?: DebuggerOptions,
|
2024-08-20 08:21:44 +08:00
|
|
|
): WatchHandle {
|
2021-07-20 06:24:18 +08:00
|
|
|
return doWatch(
|
|
|
|
effect,
|
|
|
|
null,
|
2023-02-03 09:54:15 +08:00
|
|
|
__DEV__ ? extend({}, options as any, { flush: 'post' }) : { flush: 'post' },
|
2021-07-20 06:24:18 +08:00
|
|
|
)
|
2021-07-10 09:47:15 +08:00
|
|
|
}
|
|
|
|
|
2021-07-21 04:49:54 +08:00
|
|
|
export function watchSyncEffect(
|
|
|
|
effect: WatchEffect,
|
|
|
|
options?: DebuggerOptions,
|
2024-08-20 08:21:44 +08:00
|
|
|
): WatchHandle {
|
2021-07-21 04:49:54 +08:00
|
|
|
return doWatch(
|
|
|
|
effect,
|
|
|
|
null,
|
2023-02-03 09:54:15 +08:00
|
|
|
__DEV__ ? extend({}, options as any, { flush: 'sync' }) : { flush: 'sync' },
|
2021-07-21 04:49:54 +08:00
|
|
|
)
|
|
|
|
}
|
|
|
|
|
2024-08-02 12:43:22 +08:00
|
|
|
export type MultiWatchSources = (WatchSource<unknown> | object)[]
|
2020-12-05 05:32:26 +08:00
|
|
|
|
2023-12-30 08:22:56 +08:00
|
|
|
// overload: single source + cb
|
|
|
|
export function watch<T, Immediate extends Readonly<boolean> = false>(
|
|
|
|
source: WatchSource<T>,
|
2024-06-10 16:07:25 +08:00
|
|
|
cb: WatchCallback<T, MaybeUndefined<T, Immediate>>,
|
2023-12-30 08:22:56 +08:00
|
|
|
options?: WatchOptions<Immediate>,
|
2024-08-02 14:41:27 +08:00
|
|
|
): WatchHandle
|
2023-12-30 08:22:56 +08:00
|
|
|
|
2024-06-10 16:07:25 +08:00
|
|
|
// overload: reactive array or tuple of multiple sources + cb
|
2020-02-14 01:08:21 +08:00
|
|
|
export function watch<
|
2024-06-10 16:07:25 +08:00
|
|
|
T extends Readonly<MultiWatchSources>,
|
2020-02-18 12:14:19 +08:00
|
|
|
Immediate extends Readonly<boolean> = false,
|
2020-02-14 01:08:21 +08:00
|
|
|
>(
|
2024-06-10 16:07:25 +08:00
|
|
|
sources: readonly [...T] | T,
|
|
|
|
cb: [T] extends [ReactiveMarker]
|
|
|
|
? WatchCallback<T, MaybeUndefined<T, Immediate>>
|
|
|
|
: WatchCallback<MapSources<T, false>, MapSources<T, Immediate>>,
|
2020-12-05 05:32:26 +08:00
|
|
|
options?: WatchOptions<Immediate>,
|
2024-08-02 14:41:27 +08:00
|
|
|
): WatchHandle
|
2020-12-05 05:32:26 +08:00
|
|
|
|
2024-06-10 16:07:25 +08:00
|
|
|
// overload: array of multiple sources + cb
|
2020-12-05 05:32:26 +08:00
|
|
|
export function watch<
|
2024-06-10 16:07:25 +08:00
|
|
|
T extends MultiWatchSources,
|
2020-12-05 05:32:26 +08:00
|
|
|
Immediate extends Readonly<boolean> = false,
|
|
|
|
>(
|
2024-06-10 16:07:25 +08:00
|
|
|
sources: [...T],
|
2020-10-14 03:45:17 +08:00
|
|
|
cb: WatchCallback<MapSources<T, false>, MapSources<T, Immediate>>,
|
2020-02-18 12:14:19 +08:00
|
|
|
options?: WatchOptions<Immediate>,
|
2024-08-02 14:41:27 +08:00
|
|
|
): WatchHandle
|
2019-08-19 10:49:08 +08:00
|
|
|
|
2021-02-04 14:34:40 +08:00
|
|
|
// overload: watching reactive object w/ cb
|
2020-05-04 21:27:28 +08:00
|
|
|
export function watch<
|
|
|
|
T extends object,
|
|
|
|
Immediate extends Readonly<boolean> = false,
|
|
|
|
>(
|
|
|
|
source: T,
|
2024-06-10 16:07:25 +08:00
|
|
|
cb: WatchCallback<T, MaybeUndefined<T, Immediate>>,
|
2020-05-04 21:27:28 +08:00
|
|
|
options?: WatchOptions<Immediate>,
|
2024-08-02 14:41:27 +08:00
|
|
|
): WatchHandle
|
2020-05-04 21:27:28 +08:00
|
|
|
|
2019-08-19 10:49:08 +08:00
|
|
|
// implementation
|
2020-10-14 03:45:17 +08:00
|
|
|
export function watch<T = any, Immediate extends Readonly<boolean> = false>(
|
|
|
|
source: T | WatchSource<T>,
|
|
|
|
cb: any,
|
|
|
|
options?: WatchOptions<Immediate>,
|
2024-08-02 14:41:27 +08:00
|
|
|
): WatchHandle {
|
2020-02-26 07:57:41 +08:00
|
|
|
if (__DEV__ && !isFunction(cb)) {
|
|
|
|
warn(
|
|
|
|
`\`watch(fn, options?)\` signature has been moved to a separate API. ` +
|
|
|
|
`Use \`watchEffect(fn, options?)\` instead. \`watch\` now only ` +
|
|
|
|
`supports \`watch(source, cb, options?) signature.`,
|
|
|
|
)
|
2019-08-19 10:49:08 +08:00
|
|
|
}
|
2020-10-14 03:45:17 +08:00
|
|
|
return doWatch(source as any, cb, options)
|
2019-08-19 10:49:08 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
function doWatch(
|
2020-10-14 03:45:17 +08:00
|
|
|
source: WatchSource | WatchSource[] | WatchEffect | object,
|
2019-12-31 00:30:12 +08:00
|
|
|
cb: WatchCallback | null,
|
2024-08-20 08:21:44 +08:00
|
|
|
options: WatchOptions = EMPTY_OBJ,
|
2024-08-02 14:41:27 +08:00
|
|
|
): WatchHandle {
|
2024-08-20 08:21:44 +08:00
|
|
|
const { immediate, deep, flush, once } = options
|
2023-08-30 15:25:51 +08:00
|
|
|
|
2020-02-18 12:14:19 +08:00
|
|
|
if (__DEV__ && !cb) {
|
|
|
|
if (immediate !== undefined) {
|
|
|
|
warn(
|
|
|
|
`watch() "immediate" option is only respected when using the ` +
|
2020-02-25 01:03:02 +08:00
|
|
|
`watch(source, callback, options?) signature.`,
|
2020-02-18 12:14:19 +08:00
|
|
|
)
|
|
|
|
}
|
|
|
|
if (deep !== undefined) {
|
|
|
|
warn(
|
|
|
|
`watch() "deep" option is only respected when using the ` +
|
2020-02-25 01:03:02 +08:00
|
|
|
`watch(source, callback, options?) signature.`,
|
2020-02-18 12:14:19 +08:00
|
|
|
)
|
|
|
|
}
|
2023-08-30 15:25:51 +08:00
|
|
|
if (once !== undefined) {
|
|
|
|
warn(
|
|
|
|
`watch() "once" option is only respected when using the ` +
|
|
|
|
`watch(source, callback, options?) signature.`,
|
|
|
|
)
|
|
|
|
}
|
2020-02-18 12:14:19 +08:00
|
|
|
}
|
|
|
|
|
2024-08-20 08:21:44 +08:00
|
|
|
const baseWatchOptions: BaseWatchOptions = extend({}, options)
|
2019-08-31 03:05:39 +08:00
|
|
|
|
2024-08-20 08:21:44 +08:00
|
|
|
if (__DEV__) baseWatchOptions.onWarn = warn
|
2019-06-06 15:19:04 +08:00
|
|
|
|
2024-10-04 16:09:23 +08:00
|
|
|
// immediate watcher or watchEffect
|
|
|
|
const runsImmediately = (cb && immediate) || (!cb && flush !== 'post')
|
2022-10-26 18:30:15 +08:00
|
|
|
let ssrCleanup: (() => void)[] | undefined
|
2021-09-24 03:02:19 +08:00
|
|
|
if (__SSR__ && isInSSRComponentSetup) {
|
2022-10-26 18:30:15 +08:00
|
|
|
if (flush === 'sync') {
|
2023-05-19 07:49:28 +08:00
|
|
|
const ctx = useSSRContext()!
|
2022-10-26 18:30:15 +08:00
|
|
|
ssrCleanup = ctx.__watcherHandles || (ctx.__watcherHandles = [])
|
2024-10-04 16:09:23 +08:00
|
|
|
} else if (!runsImmediately) {
|
2024-09-16 10:49:16 +08:00
|
|
|
const watchStopHandle = () => {}
|
|
|
|
watchStopHandle.stop = NOOP
|
|
|
|
watchStopHandle.resume = NOOP
|
|
|
|
watchStopHandle.pause = NOOP
|
|
|
|
return watchStopHandle
|
2022-10-26 18:30:15 +08:00
|
|
|
}
|
2020-02-18 12:14:19 +08:00
|
|
|
}
|
|
|
|
|
2024-08-20 08:21:44 +08:00
|
|
|
const instance = currentInstance
|
|
|
|
baseWatchOptions.call = (fn, type, args) =>
|
|
|
|
callWithAsyncErrorHandling(fn, instance, type, args)
|
|
|
|
|
|
|
|
// scheduler
|
|
|
|
let isPre = false
|
|
|
|
if (flush === 'post') {
|
|
|
|
baseWatchOptions.scheduler = job => {
|
|
|
|
queuePostRenderEffect(job, instance && instance.suspense)
|
2020-07-17 23:17:29 +08:00
|
|
|
}
|
2024-08-20 08:21:44 +08:00
|
|
|
} else if (flush !== 'sync') {
|
|
|
|
// default: 'pre'
|
|
|
|
isPre = true
|
|
|
|
baseWatchOptions.scheduler = (job, isFirstRun) => {
|
|
|
|
if (isFirstRun) {
|
|
|
|
job()
|
|
|
|
} else {
|
|
|
|
queueJob(job)
|
2019-05-29 23:44:59 +08:00
|
|
|
}
|
2020-07-17 23:17:29 +08:00
|
|
|
}
|
|
|
|
}
|
2019-05-29 23:44:59 +08:00
|
|
|
|
2024-08-20 08:21:44 +08:00
|
|
|
baseWatchOptions.augmentJob = (job: SchedulerJob) => {
|
|
|
|
// important: mark the job as a watcher callback so that scheduler knows
|
|
|
|
// it is allowed to self-trigger (#1727)
|
|
|
|
if (cb) {
|
|
|
|
job.flags! |= SchedulerJobFlags.ALLOW_RECURSE
|
2024-08-16 16:14:08 +08:00
|
|
|
}
|
2024-08-20 08:21:44 +08:00
|
|
|
if (isPre) {
|
|
|
|
job.flags! |= SchedulerJobFlags.PRE
|
|
|
|
if (instance) {
|
|
|
|
job.id = instance.uid
|
|
|
|
;(job as SchedulerJob).i = instance
|
|
|
|
}
|
2023-08-30 15:25:51 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-08-20 08:21:44 +08:00
|
|
|
const watchHandle = baseWatch(source, cb, baseWatchOptions)
|
2019-05-29 23:44:59 +08:00
|
|
|
|
2024-10-04 16:09:23 +08:00
|
|
|
if (__SSR__ && isInSSRComponentSetup) {
|
|
|
|
if (ssrCleanup) {
|
|
|
|
ssrCleanup.push(watchHandle)
|
|
|
|
} else if (runsImmediately) {
|
|
|
|
watchHandle()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-08-02 14:41:27 +08:00
|
|
|
return watchHandle
|
2019-05-29 23:44:59 +08:00
|
|
|
}
|
|
|
|
|
2025-05-26 04:43:37 +08:00
|
|
|
export function createCompatWatchGetter(
|
|
|
|
baseGetter: () => any,
|
|
|
|
instance: ComponentInternalInstance,
|
|
|
|
) {
|
|
|
|
return (): any => {
|
|
|
|
const val = baseGetter()
|
|
|
|
if (
|
|
|
|
isArray(val) &&
|
|
|
|
checkCompatEnabled(DeprecationTypes.WATCH_ARRAY, instance)
|
|
|
|
) {
|
|
|
|
traverse(val, 1)
|
|
|
|
}
|
|
|
|
return val
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-09-04 23:36:27 +08:00
|
|
|
// this.$watch
|
|
|
|
export function instanceWatch(
|
2019-09-07 00:58:31 +08:00
|
|
|
this: ComponentInternalInstance,
|
2019-09-04 23:36:27 +08:00
|
|
|
source: string | Function,
|
2021-04-22 21:49:25 +08:00
|
|
|
value: WatchCallback | ObjectWatchOptionItem,
|
2019-09-04 23:36:27 +08:00
|
|
|
options?: WatchOptions,
|
2024-08-02 14:41:27 +08:00
|
|
|
): WatchHandle {
|
2020-04-17 21:12:50 +08:00
|
|
|
const publicThis = this.proxy as any
|
2025-05-26 04:43:37 +08:00
|
|
|
let getter = isString(source)
|
2021-04-08 04:19:04 +08:00
|
|
|
? source.includes('.')
|
|
|
|
? createPathGetter(publicThis, source)
|
|
|
|
: () => publicThis[source]
|
2021-05-25 23:10:11 +08:00
|
|
|
: source.bind(publicThis, publicThis)
|
2021-04-22 21:49:25 +08:00
|
|
|
let cb
|
|
|
|
if (isFunction(value)) {
|
|
|
|
cb = value
|
|
|
|
} else {
|
|
|
|
cb = value.handler as Function
|
|
|
|
options = value
|
|
|
|
}
|
2025-05-26 04:43:37 +08:00
|
|
|
|
|
|
|
if (
|
|
|
|
__COMPAT__ &&
|
|
|
|
isString(source) &&
|
|
|
|
isCompatEnabled(DeprecationTypes.WATCH_ARRAY, this)
|
|
|
|
) {
|
|
|
|
const deep = options && options.deep
|
|
|
|
if (!deep) {
|
|
|
|
options = extend({ compatWatchArray: true }, options)
|
|
|
|
getter = createCompatWatchGetter(getter, this)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-01-09 07:22:13 +08:00
|
|
|
const reset = setCurrentInstance(this)
|
2021-07-21 02:32:17 +08:00
|
|
|
const res = doWatch(getter, cb.bind(publicThis), options)
|
2024-01-09 07:22:13 +08:00
|
|
|
reset()
|
2021-07-21 02:32:17 +08:00
|
|
|
return res
|
2019-09-04 23:36:27 +08:00
|
|
|
}
|
|
|
|
|
2021-04-08 04:19:04 +08:00
|
|
|
export function createPathGetter(ctx: any, path: string) {
|
|
|
|
const segments = path.split('.')
|
2024-08-08 23:05:21 +08:00
|
|
|
return (): any => {
|
2021-04-08 04:19:04 +08:00
|
|
|
let cur = ctx
|
|
|
|
for (let i = 0; i < segments.length && cur; i++) {
|
|
|
|
cur = cur[segments[i]]
|
|
|
|
}
|
|
|
|
return cur
|
|
|
|
}
|
|
|
|
}
|