import { isObject, toRawType, def, hasOwn, makeMap } from '@vue/shared' import { mutableHandlers, readonlyHandlers, shallowReactiveHandlers, shallowReadonlyHandlers } from './baseHandlers' import { mutableCollectionHandlers, readonlyCollectionHandlers, shallowCollectionHandlers } from './collectionHandlers' import { UnwrapRef, Ref } from './ref' export const enum ReactiveFlags { SKIP = '__v_skip', IS_REACTIVE = '__v_isReactive', IS_READONLY = '__v_isReadonly', RAW = '__v_raw', REACTIVE = '__v_reactive', READONLY = '__v_readonly' } interface Target { [ReactiveFlags.SKIP]?: boolean [ReactiveFlags.IS_REACTIVE]?: boolean [ReactiveFlags.IS_READONLY]?: boolean [ReactiveFlags.RAW]?: any [ReactiveFlags.REACTIVE]?: any [ReactiveFlags.READONLY]?: any } const collectionTypes = new Set([Set, Map, WeakMap, WeakSet]) const isObservableType = /*#__PURE__*/ makeMap( 'Object,Array,Map,Set,WeakMap,WeakSet' ) const canObserve = (value: Target): boolean => { return ( !value[ReactiveFlags.SKIP] && isObservableType(toRawType(value)) && !Object.isFrozen(value) ) } // only unwrap nested ref type UnwrapNestedRefs = T extends Ref ? T : UnwrapRef export function reactive(target: T): UnwrapNestedRefs export function reactive(target: object) { // if trying to observe a readonly proxy, return the readonly version. if (target && (target as Target)[ReactiveFlags.IS_READONLY]) { return target } return createReactiveObject( target, false, mutableHandlers, mutableCollectionHandlers ) } // Return a reactive-copy of the original object, where only the root level // properties are reactive, and does NOT unwrap refs nor recursively convert // returned properties. export function shallowReactive(target: T): T { return createReactiveObject( target, false, shallowReactiveHandlers, shallowCollectionHandlers ) } export function readonly( target: T ): Readonly> { return createReactiveObject( target, true, readonlyHandlers, readonlyCollectionHandlers ) } // Return a reactive-copy of the original object, where only the root level // properties are readonly, and does NOT unwrap refs nor recursively convert // returned properties. // This is used for creating the props proxy object for stateful components. export function shallowReadonly( target: T ): Readonly<{ [K in keyof T]: UnwrapNestedRefs }> { return createReactiveObject( target, true, shallowReadonlyHandlers, readonlyCollectionHandlers ) } function createReactiveObject( target: Target, isReadonly: boolean, baseHandlers: ProxyHandler, collectionHandlers: ProxyHandler ) { if (!isObject(target)) { if (__DEV__) { console.warn(`value cannot be made reactive: ${String(target)}`) } return target } // target is already a Proxy, return it. // exception: calling readonly() on a reactive object if ( target[ReactiveFlags.RAW] && !(isReadonly && target[ReactiveFlags.IS_REACTIVE]) ) { return target } // target already has corresponding Proxy if ( hasOwn(target, isReadonly ? ReactiveFlags.READONLY : ReactiveFlags.REACTIVE) ) { return isReadonly ? target[ReactiveFlags.READONLY] : target[ReactiveFlags.REACTIVE] } // only a whitelist of value types can be observed. if (!canObserve(target)) { return target } const observed = new Proxy( target, collectionTypes.has(target.constructor) ? collectionHandlers : baseHandlers ) def( target, isReadonly ? ReactiveFlags.READONLY : ReactiveFlags.REACTIVE, observed ) return observed } export function isReactive(value: unknown): boolean { if (isReadonly(value)) { return isReactive((value as Target)[ReactiveFlags.RAW]) } return !!(value && (value as Target)[ReactiveFlags.IS_REACTIVE]) } export function isReadonly(value: unknown): boolean { return !!(value && (value as Target)[ReactiveFlags.IS_READONLY]) } export function isProxy(value: unknown): boolean { return isReactive(value) || isReadonly(value) } export function toRaw(observed: T): T { return ( (observed && toRaw((observed as Target)[ReactiveFlags.RAW])) || observed ) } export function markRaw(value: T): T { def(value, ReactiveFlags.SKIP, true) return value }