import { reactive, readonly, toRaw, ReactiveFlags, Target, readonlyMap, reactiveMap } from './reactive' import { TrackOpTypes, TriggerOpTypes } from './operations' import { track, trigger, ITERATE_KEY, pauseTracking, resetTracking } from './effect' import { isObject, hasOwn, isSymbol, hasChanged, isArray, isIntegerKey, extend } from '@vue/shared' import { isRef } from './ref' const builtInSymbols = new Set( Object.getOwnPropertyNames(Symbol) .map(key => (Symbol as any)[key]) .filter(isSymbol) ) const get = /*#__PURE__*/ createGetter() const shallowGet = /*#__PURE__*/ createGetter(false, true) const readonlyGet = /*#__PURE__*/ createGetter(true) const shallowReadonlyGet = /*#__PURE__*/ createGetter(true, true) const arrayInstrumentations: Record = {} // instrument identity-sensitive Array methods to account for possible reactive // values ;(['includes', 'indexOf', 'lastIndexOf'] as const).forEach(key => { const method = Array.prototype[key] as any arrayInstrumentations[key] = function(this: unknown[], ...args: unknown[]) { const arr = toRaw(this) for (let i = 0, l = this.length; i < l; i++) { track(arr, TrackOpTypes.GET, i + '') } // we run the method using the original args first (which may be reactive) const res = method.apply(arr, args) if (res === -1 || res === false) { // if that didn't work, run it again using raw values. return method.apply(arr, args.map(toRaw)) } else { return res } } }) // instrument length-altering mutation methods to avoid length being tracked // which leads to infinite loops in some cases (#2137) ;(['push', 'pop', 'shift', 'unshift', 'splice'] as const).forEach(key => { const method = Array.prototype[key] as any arrayInstrumentations[key] = function(this: unknown[], ...args: unknown[]) { pauseTracking() const res = method.apply(this, args) resetTracking() return res } }) function createGetter(isReadonly = false, shallow = false) { return function get(target: Target, key: string | symbol, receiver: object) { if (key === ReactiveFlags.IS_REACTIVE) { return !isReadonly } else if (key === ReactiveFlags.IS_READONLY) { return isReadonly } else if ( key === ReactiveFlags.RAW && receiver === (isReadonly ? readonlyMap : reactiveMap).get(target) ) { return target } const targetIsArray = isArray(target) if (targetIsArray && hasOwn(arrayInstrumentations, key)) { return Reflect.get(arrayInstrumentations, key, receiver) } const res = Reflect.get(target, key, receiver) if ( isSymbol(key) ? builtInSymbols.has(key as symbol) : key === `__proto__` || key === `__v_isRef` ) { return res } if (!isReadonly) { track(target, TrackOpTypes.GET, key) } if (shallow) { return res } if (isRef(res)) { // ref unwrapping - does not apply for Array + integer key. const shouldUnwrap = !targetIsArray || !isIntegerKey(key) return shouldUnwrap ? res.value : res } if (isObject(res)) { // Convert returned value into a proxy as well. we do the isObject check // here to avoid invalid value warning. Also need to lazy access readonly // and reactive here to avoid circular dependency. return isReadonly ? readonly(res) : reactive(res) } return res } } const set = /*#__PURE__*/ createSetter() const shallowSet = /*#__PURE__*/ createSetter(true) function createSetter(shallow = false) { return function set( target: object, key: string | symbol, value: unknown, receiver: object ): boolean { const oldValue = (target as any)[key] if (!shallow) { value = toRaw(value) if (!isArray(target) && isRef(oldValue) && !isRef(value)) { oldValue.value = value return true } } else { // in shallow mode, objects are set as-is regardless of reactive or not } const hadKey = isArray(target) && isIntegerKey(key) ? Number(key) < target.length : hasOwn(target, key) const result = Reflect.set(target, key, value, receiver) // don't trigger if target is something up in the prototype chain of original if (target === toRaw(receiver)) { if (!hadKey) { trigger(target, TriggerOpTypes.ADD, key, value) } else if (hasChanged(value, oldValue)) { trigger(target, TriggerOpTypes.SET, key, value, oldValue) } } return result } } function deleteProperty(target: object, key: string | symbol): boolean { const hadKey = hasOwn(target, key) const oldValue = (target as any)[key] const result = Reflect.deleteProperty(target, key) if (result && hadKey) { trigger(target, TriggerOpTypes.DELETE, key, undefined, oldValue) } return result } function has(target: object, key: string | symbol): boolean { const result = Reflect.has(target, key) if (!isSymbol(key) || !builtInSymbols.has(key)) { track(target, TrackOpTypes.HAS, key) } return result } function ownKeys(target: object): (string | number | symbol)[] { track(target, TrackOpTypes.ITERATE, isArray(target) ? 'length' : ITERATE_KEY) return Reflect.ownKeys(target) } export const mutableHandlers: ProxyHandler = { get, set, deleteProperty, has, ownKeys } export const readonlyHandlers: ProxyHandler = { get: readonlyGet, set(target, key) { if (__DEV__) { console.warn( `Set operation on key "${String(key)}" failed: target is readonly.`, target ) } return true }, deleteProperty(target, key) { if (__DEV__) { console.warn( `Delete operation on key "${String(key)}" failed: target is readonly.`, target ) } return true } } export const shallowReactiveHandlers: ProxyHandler = extend( {}, mutableHandlers, { get: shallowGet, set: shallowSet } ) // Props handlers are special in the sense that it should not unwrap top-level // refs (in order to allow refs to be explicitly passed down), but should // retain the reactivity of the normal readonly object. export const shallowReadonlyHandlers: ProxyHandler = extend( {}, readonlyHandlers, { get: shallowReadonlyGet } )