import { toRaw , reactive , readonly , ReactiveFlags } from './reactive' ; import { track , trigger , ITERATE_KEY , MAP_KEY_ITERATE_KEY } from './effect' ; import { TrackOpTypes , TriggerOpTypes } from './operations' ; import { isObject , capitalize , hasOwn , hasChanged , toRawType , isMap } from '@vue/shared' ; export type CollectionTypes = IterableCollections | WeakCollections ; type IterableCollections = Map < any , any > | Set < any > ; type WeakCollections = WeakMap < any , any > | WeakSet < any > ; type MapTypes = Map < any , any > | WeakMap < any , any > ; type SetTypes = Set < any > | WeakSet < any > ; const toReactive = < T extends unknown > ( value ) : T => isObject ( value ) ? reactive ( value ) : value ; const toReadonly = < T extends unknown > ( value ) : T => isObject ( value ) ? readonly ( value as Record < any , any > ) : value ; const toShallow = < T extends unknown > ( value ) : T => value ; const getProto = < T extends CollectionTypes > ( v ) : any => Reflect . getPrototypeOf ( v ) ; function get ( target , key , isReadonly = false , isShallow = false ) { target = ( target as any ) [ ReactiveFlags . RAW ] ; const rawTarget = toRaw ( target ) ; const rawKey = toRaw ( key ) ; if ( key !== rawKey ) { ! isReadonly && track ( rawTarget , TrackOpTypes . GET , key ) ; } ! isReadonly && track ( rawTarget , TrackOpTypes . GET , rawKey ) ; const { has } = getProto ( rawTarget ) ; const wrap = isShallow ? toShallow : isReadonly ? toReadonly : toReactive ; if ( has . call ( rawTarget , key ) ) { return wrap ( target . get ( key ) ) ; } else if ( has . call ( rawTarget , rawKey ) ) { return wrap ( target . get ( rawKey ) ) ; } else if ( target !== rawTarget ) { target . get ( key ) ; } } function has ( this , key , isReadonly = false ) { const target = ( this as any ) [ ReactiveFlags . RAW ] ; const rawTarget = toRaw ( target ) ; const rawKey = toRaw ( key ) ; if ( key !== rawKey ) { ! isReadonly && track ( rawTarget , TrackOpTypes . HAS , key ) ; } ! isReadonly && track ( rawTarget , TrackOpTypes . HAS , rawKey ) ; return key === rawKey ? target . has ( key ) : target . has ( key ) || target . has ( rawKey ) ; } function size ( target , isReadonly = false ) { target = ( target as any ) [ ReactiveFlags . RAW ] ; ! isReadonly && track ( toRaw ( target ) , TrackOpTypes . ITERATE , ITERATE_KEY ) ; return Reflect . get ( target , 'size' , target ) ; } function add ( this , value ) { value = toRaw ( value ) ; const target = toRaw ( this ) ; const proto = getProto ( target ) ; const hadKey = proto . has . call ( target , value ) ; if ( ! hadKey ) { target . add ( value ) ; trigger ( target , TriggerOpTypes . ADD , value , value ) ; } return this ; } function set ( this , key , value ) { value = toRaw ( value ) ; const target = toRaw ( this ) ; const { has , get } = getProto ( target ) ; let hadKey = has . call ( target , key ) ; if ( ! hadKey ) { key = toRaw ( key ) ; hadKey = has . call ( target , key ) ; } else if ( __DEV__ ) { checkIdentityKeys ( target , has , key ) ; } const oldValue = get . call ( target , key ) ; target . set ( key , value ) ; if ( ! hadKey ) { trigger ( target , TriggerOpTypes . ADD , key , value ) ; } else if ( hasChanged ( value , oldValue ) ) { trigger ( target , TriggerOpTypes . SET , key , value , oldValue ) ; } return this ; } function deleteEntry ( this , key ) { const target = toRaw ( this ) ; const { has , get } = getProto ( target ) ; let hadKey = has . call ( target , key ) ; if ( ! hadKey ) { key = toRaw ( key ) ; hadKey = has . call ( target , key ) ; } else if ( __DEV__ ) { checkIdentityKeys ( target , has , key ) ; } const oldValue = get ? get . call ( target , key ) : undefined ; const result = target . delete ( key ) ; if ( hadKey ) { trigger ( target , TriggerOpTypes . DELETE , key , undefined , oldValue ) ; } return result ; } function clear ( this ) { const target = toRaw ( this ) ; const hadItems = target . size !== 0 ; const oldTarget = __DEV__ ? isMap ( target ) ? new Map ( target ) : new Set ( target ) : undefined ; const result = target . clear ( ) ; if ( hadItems ) { trigger ( target , TriggerOpTypes . CLEAR , undefined , undefined , oldTarget ) ; } return result ; } function createForEach ( isReadonly , isShallow ) { return function forEach ( this , callback , thisArg ? ) { const observed = this as any ; const target = observed [ ReactiveFlags . RAW ] ; const rawTarget = toRaw ( target ) ; const wrap = isShallow ? toShallow : isReadonly ? toReadonly : toReactive ; ! isReadonly && track ( rawTarget , TrackOpTypes . ITERATE , ITERATE_KEY ) ; return target . forEach ( ( value , key ) => { return callback . call ( thisArg , wrap ( value ) , wrap ( key ) , observed ) ; } ) ; } ; } interface Iterable { [ Symbol . iterator ] ( ) : Iterator ; } interface Iterator { next ( value ? ) : IterationResult ; } interface IterationResult { value : any ; done : boolean ; } function createIterableMethod ( method , isReadonly , isShallow ) { return function ( this , ... args ) : Iterable & Iterator { const target = ( this as any ) [ ReactiveFlags . RAW ] ; const rawTarget = toRaw ( target ) ; const targetIsMap = isMap ( rawTarget ) ; const isPair = method === 'entries' || ( method === Symbol . iterator && targetIsMap ) ; const isKeyOnly = method === 'keys' && targetIsMap ; const innerIterator = target [ method ] ( ... args ) ; const wrap = isShallow ? toShallow : isReadonly ? toReadonly : toReactive ; ! isReadonly && track ( rawTarget , TrackOpTypes . ITERATE , isKeyOnly ? MAP_KEY_ITERATE_KEY : ITERATE_KEY ) ; return { next ( ) { const { value , done } = innerIterator . next ( ) ; return done ? { value , done } : { value : isPair ? [ wrap ( value [ 0 ] ) , wrap ( value [ 1 ] ) ] : wrap ( value ) , done } ; } , [ Symbol . iterator ] ( ) { return this ; } } ; } ; } function createReadonlyMethod ( type ) { return function ( this , ... args ) { if ( __DEV__ ) { const key = args [ 0 ] ? `on key "${ args [ 0 ] }" ` : `` ; console . warn ( `${ capitalize ( type ) } operation ${ key }failed: target is readonly.` , toRaw ( this ) ) ; } return type === TriggerOpTypes . DELETE ? false : this ; } ; } function createInstrumentations ( ) { const mutableInstrumentations = { get ( this , key ) { return get ( this , key ) ; } , get size ( ) { return size ( this as unknown as IterableCollections ) ; } , has , add , set , delete : deleteEntry , clear , forEach : createForEach ( false , false ) } ; const shallowInstrumentations = { get ( this , key ) { return get ( this , key , false , true ) ; } , get size ( ) { return size ( this as unknown as IterableCollections ) ; } , has , add , set , delete : deleteEntry , clear , forEach : createForEach ( false , true ) } ; const readonlyInstrumentations = { get ( this , key ) { return get ( this , key , true ) ; } , get size ( ) { return size ( this as unknown as IterableCollections , true ) ; } , has ( this , key ) { return has . call ( this , key , true ) ; } , add : createReadonlyMethod ( TriggerOpTypes . ADD ) , set : createReadonlyMethod ( TriggerOpTypes . SET ) , delete : createReadonlyMethod ( TriggerOpTypes . DELETE ) , clear : createReadonlyMethod ( TriggerOpTypes . CLEAR ) , forEach : createForEach ( true , false ) } ; const shallowReadonlyInstrumentations = { get ( this , key ) { return get ( this , key , true , true ) ; } , get size ( ) { return size ( this as unknown as IterableCollections , true ) ; } , has ( this , key ) { return has . call ( this , key , true ) ; } , add : createReadonlyMethod ( TriggerOpTypes . ADD ) , set : createReadonlyMethod ( TriggerOpTypes . SET ) , delete : createReadonlyMethod ( TriggerOpTypes . DELETE ) , clear : createReadonlyMethod ( TriggerOpTypes . CLEAR ) , forEach : createForEach ( true , true ) } ; const iteratorMethods = [ 'keys' , 'values' , 'entries' , Symbol . iterator ] ; iteratorMethods . forEach ( method => { mutableInstrumentations [ method as string ] = createIterableMethod ( method , false , false ) ; readonlyInstrumentations [ method as string ] = createIterableMethod ( method , true , false ) ; shallowInstrumentations [ method as string ] = createIterableMethod ( method , false , true ) ; shallowReadonlyInstrumentations [ method as string ] = createIterableMethod ( method , true , true ) ; } ) ; return [ mutableInstrumentations , readonlyInstrumentations , shallowInstrumentations , shallowReadonlyInstrumentations ] ; } const [ mutableInstrumentations , readonlyInstrumentations , shallowInstrumentations , shallowReadonlyInstrumentations ] = createInstrumentations ( ) ; function createInstrumentationGetter ( isReadonly , shallow ) { const instrumentations = shallow ? isReadonly ? shallowReadonlyInstrumentations : shallowInstrumentations : isReadonly ? readonlyInstrumentations : mutableInstrumentations ; return ( target , key , receiver ) => { if ( key === ReactiveFlags . IS_REACTIVE ) { return ! isReadonly ; } else if ( key === ReactiveFlags . IS_READONLY ) { return isReadonly ; } else if ( key === ReactiveFlags . RAW ) { return target ; } return Reflect . get ( hasOwn ( instrumentations , key ) && key in target ? instrumentations : target , key , receiver ) ; } ; } export const mutableCollectionHandlers = { get : createInstrumentationGetter ( false , false ) } ; export const shallowCollectionHandlers = { get : createInstrumentationGetter ( false , true ) } ; export const readonlyCollectionHandlers = { get : createInstrumentationGetter ( true , false ) } ; export const shallowReadonlyCollectionHandlers = { get : createInstrumentationGetter ( true , true ) } ; function checkIdentityKeys ( target , has , key ) { const rawKey = toRaw ( key ) ; if ( rawKey !== key && has . call ( target , rawKey ) ) { const type = toRawType ( target ) ; console . warn ( `Reactive ${ type } contains both the raw and reactive ` + `versions of the same object${ type === `Map` ? ` as keys` : `` }, ` + `which can lead to inconsistencies. ` + `Avoid differentiating between the raw and reactive versions ` + `of an object and only use the reactive version if possible.` ) ; } }