import { effect , Ref , ComputedRef , ReactiveEffectOptions , isRef , isReactive , stop } from '@vue/reactivity' ; import { isFunction , isArray , NOOP , isObject , remove , hasChanged } from '@vue/shared' ; import { currentInstance , recordInstanceBoundEffect } from './component' ; import { warn , callWithErrorHandling , callWithAsyncErrorHandling } from './errorHandling' ; 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 ; export type WatchStopHandle = ( ) => void ; type MapSources < T > = { [ K in keyof T ] : T [ K ] extends WatchSource < infer V > ? V : T [ K ] extends object ? T [ K ] : never ; } ; type MapOldSources < 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 ; const INITIAL_WATCHER_VALUE = { } ; export interface WatchOptionsBase { flush ? : 'pre' | 'post' | 'sync' ; onTrack ? : ReactiveEffectOptions [ 'onTrack' ] ; onTrigger ? : ReactiveEffectOptions [ 'onTrigger' ] ; } export interface WatchOptions < Immediate = boolean > extends WatchOptionsBase { immediate ? : Immediate ; deep ? : boolean ; } export function watchEffect ( effect , options ? ) { return doWatch ( effect , null , options ) ; } export function watch < T extends Readonly < Array < WatchSource < unknown > | object > > , Immediate extends Readonly < boolean > = false > ( sources , 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 > ( source , cb , options ? ) { return doWatch ( source , cb , options ) ; } function doWatch ( source , cb , { immediate , deep , flush , onTrack , onTrigger } = { } ) { const instance = currentInstance ; let getter ; let forceTrigger = false ; let isMultiSource = false ; if ( isRef ( source ) ) { getter = ( ) => ( source as Ref ) . value ; forceTrigger = ! ! ( source as Ref ) . _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 , 'watch getter' ) ; else __DEV__ && warn ( 'invalid source' ) ; } ) ; } else if ( isFunction ( source ) ) { if ( cb ) { getter = ( ) => callWithErrorHandling ( source , instance , 'watch getter' ) ; } else { getter = ( ) => { if ( instance && instance . isUnmounted ) return ; if ( cleanup ) cleanup ( ) ; return callWithErrorHandling ( source , instance , 'watch callback' , [ onInvalidate ] ) ; } ; } } else { getter = NOOP ; } if ( cb && deep ) { const baseGetter = getter ; getter = ( ) => traverse ( baseGetter ( ) ) ; } let cleanup ; const onInvalidate = ( fn ) => { cleanup = runner . options . onStop = ( ) => { callWithErrorHandling ( fn , instance , 'watch cleanup' ) ; } ; } ; let oldValue = isMultiSource ? [ ] : INITIAL_WATCHER_VALUE ; const job = ( ) => { if ( ! runner . active ) return ; if ( cb ) { const newValue = runner ( ) ; if ( deep || forceTrigger || ( isMultiSource ? ( newValue as any [ ] ) . some ( ( v , i ) => hasChanged ( v , ( oldValue as any [ ] ) [ i ] ) ) : hasChanged ( newValue , oldValue ) ) ) { if ( cleanup ) cleanup ( ) ; callWithAsyncErrorHandling ( cb , instance , 'watch callback' , [ newValue , oldValue === INITIAL_WATCHER_VALUE ? undefined : oldValue , onInvalidate , ] ) ; oldValue = newValue ; } } else { runner ( ) ; } } ; job . allowRecurse = ! ! cb ; let scheduler ; if ( flush === 'sync' ) { scheduler = job ; } else if ( flush === 'post' ) { scheduler = ( ) => job ( ) ; } else { scheduler = ( ) => { if ( ! instance ) { job ( ) ; } else { job ( ) ; } } ; } const runner = effect ( getter , { lazy : true , onTrack , onTrigger , scheduler , } ) ; recordInstanceBoundEffect ( runner ) ; if ( cb ) { if ( immediate ) job ( ) ; else oldValue = runner ( ) ; } else { runner ( ) ; } return ( ) => { stop ( runner ) ; if ( instance ) remove ( instance . effects ! , runner ) ; } ; } function traverse ( value , seen = new Set ( ) ) { if ( ! isObject ( value ) || seen . has ( value ) ) return value ; seen . add ( value ) ; if ( isArray ( value ) ) { for ( let i = 0 ; i < value . length ; i ++ ) traverse ( value [ i ] , seen ) ; } else if ( value instanceof Map ) { value . forEach ( ( _ , key ) => { traverse ( value . get ( key ) , seen ) ; } ) ; } else if ( value instanceof Set ) { value . forEach ( ( v ) => { traverse ( v , seen ) ; } ) ; } else { for ( const key of Object . keys ( value ) ) traverse ( value [ key ] , seen ) ; } return value ; }