import { type WatchOptions as BaseWatchOptions, type DebuggerOptions, type ReactiveMarker, type WatchCallback, type WatchEffect, type WatchHandle, type WatchSource, watch as baseWatch, } from '@vue/reactivity' import { type SchedulerJob, SchedulerJobFlags, queueJob } from './scheduler' import { EMPTY_OBJ, NOOP, extend, isFunction, isString } from '@vue/shared' import { type ComponentInternalInstance, currentInstance, isInSSRComponentSetup, setCurrentInstance, } from './component' import { callWithAsyncErrorHandling } from './errorHandling' import { queuePostRenderEffect } from './renderer' import { warn } from './warning' import type { ObjectWatchOptionItem } from './componentOptions' import { useSSRContext } from './helpers/useSsrContext' export type { WatchHandle, WatchStopHandle, WatchEffect, WatchSource, WatchCallback, OnCleanup, } from '@vue/reactivity' type MaybeUndefined = I extends true ? T | undefined : T type MapSources = { [K in keyof T]: T[K] extends WatchSource ? MaybeUndefined : T[K] extends object ? MaybeUndefined : never } export interface WatchEffectOptions extends DebuggerOptions { flush?: 'pre' | 'post' | 'sync' } export interface WatchOptions extends WatchEffectOptions { immediate?: Immediate deep?: boolean | number once?: boolean } // Simple effect. export function watchEffect( effect: WatchEffect, options?: WatchEffectOptions, ): WatchHandle { return doWatch(effect, null, options) } export function watchPostEffect( effect: WatchEffect, options?: DebuggerOptions, ): WatchHandle { return doWatch( effect, null, __DEV__ ? extend({}, options as any, { flush: 'post' }) : { flush: 'post' }, ) } export function watchSyncEffect( effect: WatchEffect, options?: DebuggerOptions, ): WatchHandle { return doWatch( effect, null, __DEV__ ? extend({}, options as any, { flush: 'sync' }) : { flush: 'sync' }, ) } export type MultiWatchSources = (WatchSource | object)[] // overload: single source + cb export function watch = false>( source: WatchSource, cb: WatchCallback>, options?: WatchOptions, ): WatchHandle // overload: reactive array or tuple of multiple sources + cb export function watch< T extends Readonly, Immediate extends Readonly = false, >( sources: readonly [...T] | T, cb: [T] extends [ReactiveMarker] ? WatchCallback> : WatchCallback, MapSources>, options?: WatchOptions, ): WatchHandle // overload: array of multiple sources + cb export function watch< T extends MultiWatchSources, Immediate extends Readonly = false, >( sources: [...T], cb: WatchCallback, MapSources>, options?: WatchOptions, ): WatchHandle // overload: watching reactive object w/ cb export function watch< T extends object, Immediate extends Readonly = false, >( source: T, cb: WatchCallback>, options?: WatchOptions, ): WatchHandle // implementation export function watch = false>( source: T | WatchSource, cb: any, options?: WatchOptions, ): WatchHandle { 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.`, ) } return doWatch(source as any, cb, options) } function doWatch( source: WatchSource | WatchSource[] | WatchEffect | object, cb: WatchCallback | null, options: WatchOptions = EMPTY_OBJ, ): WatchHandle { const { immediate, deep, flush, once } = options if (__DEV__ && !cb) { if (immediate !== undefined) { warn( `watch() "immediate" option is only respected when using the ` + `watch(source, callback, options?) signature.`, ) } if (deep !== undefined) { warn( `watch() "deep" option is only respected when using the ` + `watch(source, callback, options?) signature.`, ) } if (once !== undefined) { warn( `watch() "once" option is only respected when using the ` + `watch(source, callback, options?) signature.`, ) } } const baseWatchOptions: BaseWatchOptions = extend({}, options) if (__DEV__) baseWatchOptions.onWarn = warn // immediate watcher or watchEffect const runsImmediately = (cb && immediate) || (!cb && flush !== 'post') let ssrCleanup: (() => void)[] | undefined if (__SSR__ && isInSSRComponentSetup) { if (flush === 'sync') { const ctx = useSSRContext()! ssrCleanup = ctx.__watcherHandles || (ctx.__watcherHandles = []) } else if (!runsImmediately) { const watchStopHandle = () => {} watchStopHandle.stop = NOOP watchStopHandle.resume = NOOP watchStopHandle.pause = NOOP return watchStopHandle } } 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) } } else if (flush !== 'sync') { // default: 'pre' isPre = true baseWatchOptions.scheduler = (job, isFirstRun) => { if (isFirstRun) { job() } else { queueJob(job) } } } 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 } if (isPre) { job.flags! |= SchedulerJobFlags.PRE if (instance) { job.id = instance.uid ;(job as SchedulerJob).i = instance } } } const watchHandle = baseWatch(source, cb, baseWatchOptions) if (__SSR__ && isInSSRComponentSetup) { if (ssrCleanup) { ssrCleanup.push(watchHandle) } else if (runsImmediately) { watchHandle() } } return watchHandle } // this.$watch export function instanceWatch( this: ComponentInternalInstance, source: string | Function, value: WatchCallback | ObjectWatchOptionItem, options?: WatchOptions, ): WatchHandle { const publicThis = this.proxy as any const getter = isString(source) ? source.includes('.') ? createPathGetter(publicThis, source) : () => publicThis[source] : source.bind(publicThis, publicThis) let cb if (isFunction(value)) { cb = value } else { cb = value.handler as Function options = value } const reset = setCurrentInstance(this) const res = doWatch(getter, cb.bind(publicThis), options) reset() return res } export function createPathGetter(ctx: any, path: string) { const segments = path.split('.') return (): any => { let cur = ctx for (let i = 0; i < segments.length && cur; i++) { cur = cur[segments[i]] } return cur } }