import { isRef , Ref , ComputedRef , ReactiveEffect , isReactive , ReactiveFlags , EffectScheduler , DebuggerOptions } from '@vue/reactivity' ; import { SchedulerJob , queuePreFlushCb } from './scheduler' ; import { EMPTY_OBJ , isObject , isArray , isFunction , isString , hasChanged , NOOP , remove , isMap , isSet , isPlainObject } from '@vue/shared' ; import { currentInstance , ComponentInternalInstance , isInSSRComponentSetup , setCurrentInstance , unsetCurrentInstance } from './component' ; import { ErrorCodes , callWithErrorHandling , callWithAsyncErrorHandling } from './errorHandling' ; import { queuePostRenderEffect } from './renderer' ; import { warn } from './warning' ; import { DeprecationTypes } from './compat/compatConfig' ; import { checkCompatEnabled , isCompatEnabled } from './compat/compatConfig' ; import { ObjectWatchOptionItem } from './componentOptions' ; export type WatchEffect = ( onInvalidate : InvalidateCbRegistrator ) => void ; export type WatchSource < T = any > = Ref < T > | ComputedRef < T > | ( ( ) => T ) ; export type WatchCallback < V = any , OV = any > = ( value : V , oldValue : OV , onInvalidate : InvalidateCbRegistrator ) => any ; type MapSources < T , Immediate > = { [ K in keyof T ] : T [ K ] extends WatchSource < infer V > ? Immediate extends true ? V | undefined : V : T [ K ] extends object ? Immediate extends true ? T [ K ] | undefined : T [ K ] : never ; } ; type InvalidateCbRegistrator = ( cb : ( ) => void ) => void ; export interface WatchOptionsBase extends DebuggerOptions { flush ? : 'pre' | 'post' | 'sync' ; } export interface WatchOptions < Immediate = boolean > extends WatchOptionsBase { immediate ? : Immediate ; deep ? : boolean ; } export type WatchStopHandle = ( ) => void ; export function watchEffect ( effect , options ? ) { return doWatch ( effect , null , options ) ; } export function watchPostEffect ( effect , options ? ) { return doWatch ( effect , null , ( __DEV__ ? Object . assign ( options || { } , { flush : 'post' } ) : { flush : 'post' } ) as WatchOptionsBase ) ; } export function watchSyncEffect ( effect , options ? ) { return doWatch ( effect , null , ( __DEV__ ? Object . assign ( options || { } , { flush : 'sync' } ) : { flush : 'sync' } ) as WatchOptionsBase ) ; } const INITIAL_WATCHER_VALUE = { } ; type MultiWatchSources = ( WatchSource < unknown > | object ) [ ] ; export function watch < T extends MultiWatchSources , Immediate extends Readonly < boolean > = false > ( sources , cb , options ? ) ; export function watch < T extends Readonly < MultiWatchSources > , Immediate extends Readonly < boolean > = false > ( source , cb , options ? ) ; export function watch < T , Immediate extends Readonly < boolean > = false > ( source , cb , options ? ) ; export function watch < T extends object , Immediate extends Readonly < boolean > = false > ( source , cb , options ? ) ; export function watch < T = any , Immediate extends Readonly < boolean > = false > ( source , cb , options ? ) { 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 , cb , { immediate , deep , flush , onTrack , onTrigger } = EMPTY_OBJ ) { 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.` ) ; } } const warnInvalidSource = ( s ) => { warn ( `Invalid watch source: ` , s , `A watch source can only be a getter/effect function, a ref, ` + `a reactive object, or an array of these types.` ) ; } ; const instance = currentInstance ; let getter ; let forceTrigger = false ; let isMultiSource = false ; if ( isRef ( source ) ) { getter = ( ) => source . value ; forceTrigger = ! ! source . _shallow ; } else if ( isReactive ( source ) ) { getter = ( ) => source ; deep = true ; } else if ( isArray ( source ) ) { isMultiSource = true ; forceTrigger = source . some ( isReactive ) ; getter = ( ) => source . map ( s => { if ( isRef ( s ) ) { return s . value ; } else if ( isReactive ( s ) ) { return traverse ( s ) ; } else if ( isFunction ( s ) ) { return callWithErrorHandling ( s , instance , ErrorCodes . WATCH_GETTER ) ; } else { __DEV__ && warnInvalidSource ( s ) ; } } ) ; } else if ( isFunction ( source ) ) { if ( cb ) { getter = ( ) => callWithErrorHandling ( source , instance , ErrorCodes . WATCH_GETTER ) ; } else { getter = ( ) => { if ( instance && instance . isUnmounted ) { return ; } if ( cleanup ) { cleanup ( ) ; } return callWithAsyncErrorHandling ( source , instance , ErrorCodes . WATCH_CALLBACK , [ onInvalidate ] ) ; } ; } } else { getter = NOOP ; __DEV__ && warnInvalidSource ( source ) ; } if ( __COMPAT__ && cb && ! deep ) { const baseGetter = getter ; getter = ( ) => { const val = baseGetter ( ) ; if ( isArray ( val ) && checkCompatEnabled ( DeprecationTypes . WATCH_ARRAY , instance ) ) { traverse ( val ) ; } return val ; } ; } if ( cb && deep ) { const baseGetter = getter ; getter = ( ) => traverse ( baseGetter ( ) ) ; } let cleanup ; let onInvalidate = ( fn ) => { cleanup = effect . onStop = ( ) => { callWithErrorHandling ( fn , instance , ErrorCodes . WATCH_CLEANUP ) ; } ; } ; if ( __NODE_JS__ && isInSSRComponentSetup ) { onInvalidate = NOOP ; if ( ! cb ) { getter ( ) ; } else if ( immediate ) { callWithAsyncErrorHandling ( cb , instance , ErrorCodes . WATCH_CALLBACK , [ getter ( ) , isMultiSource ? [ ] : undefined , onInvalidate ] ) ; } return NOOP ; } let oldValue = isMultiSource ? [ ] : INITIAL_WATCHER_VALUE ; const job = ( ) => { if ( ! effect . active ) { return ; } if ( cb ) { const newValue = effect . run ( ) ; if ( deep || forceTrigger || ( isMultiSource ? ( newValue as any [ ] ) . some ( ( v , i ) => hasChanged ( v , ( oldValue as any [ ] ) [ i ] ) ) : hasChanged ( newValue , oldValue ) ) || ( __COMPAT__ && isArray ( newValue ) && isCompatEnabled ( DeprecationTypes . WATCH_ARRAY , instance ) ) ) { if ( cleanup ) { cleanup ( ) ; } callWithAsyncErrorHandling ( cb , instance , ErrorCodes . WATCH_CALLBACK , [ newValue , oldValue === INITIAL_WATCHER_VALUE ? undefined : oldValue , onInvalidate ] ) ; oldValue = newValue ; } } else { effect . run ( ) ; } } ; job . allowRecurse = ! ! cb ; let scheduler ; if ( flush === 'sync' ) { scheduler = job as any ; } else if ( flush === 'post' ) { scheduler = ( ) => queuePostRenderEffect ( job , instance && instance . suspense ) ; } else { scheduler = ( ) => { if ( ! instance || instance . isMounted ) { queuePreFlushCb ( job ) ; } else { job ( ) ; } } ; } const effect = new ReactiveEffect ( getter , scheduler ) ; if ( __DEV__ ) { effect . onTrack = onTrack ; effect . onTrigger = onTrigger ; } if ( cb ) { if ( immediate ) { job ( ) ; } else { oldValue = effect . run ( ) ; } } else if ( flush === 'post' ) { queuePostRenderEffect ( effect . run . bind ( effect ) , instance && instance . suspense ) ; } else { effect . run ( ) ; } return ( ) => { effect . stop ( ) ; if ( instance && instance . scope ) { remove ( instance . scope . effects ! , effect ) ; } } ; } export function instanceWatch ( this , source , value , options ? ) { 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 cur = currentInstance ; setCurrentInstance ( this ) ; const res = doWatch ( getter , cb . bind ( publicThis ) , options ) ; if ( cur ) { setCurrentInstance ( cur ) ; } else { unsetCurrentInstance ( ) ; } return res ; } export function createPathGetter ( ctx , path ) { const segments = path . split ( '.' ) ; return ( ) => { let cur = ctx ; for ( let i = 0 ; i < segments . length && cur ; i ++ ) { cur = cur [ segments [ i ] ] ; } return cur ; } ; } export function traverse ( value , seen = new Set ( ) ) { if ( ! isObject ( value ) || ( value as any ) [ ReactiveFlags . SKIP ] ) { return value ; } seen = seen || new Set ( ) ; if ( seen . has ( value ) ) { return value ; } seen . add ( value ) ; if ( isRef ( value ) ) { traverse ( value . value , seen ) ; } else if ( isArray ( value ) ) { for ( let i = 0 ; i < value . length ; i ++ ) { traverse ( value [ i ] , seen ) ; } } else if ( isSet ( value ) || isMap ( value ) ) { value . forEach ( ( v ) => { traverse ( v , seen ) ; } ) ; } else if ( isPlainObject ( value ) ) { for ( const key in value ) { traverse ( ( value as any ) [ key ] , seen ) ; } } return value ; }