import { isObject, toRawType, def, hasOwn } 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' } export interface Target { [ReactiveFlags.SKIP]?: boolean [ReactiveFlags.IS_REACTIVE]?: boolean [ReactiveFlags.IS_READONLY]?: boolean [ReactiveFlags.RAW]?: any [ReactiveFlags.REACTIVE]?: any [ReactiveFlags.READONLY]?: any } const enum TargetType { INVALID = 0, COMMON = 1, COLLECTION = 2 } function targetTypeMap(rawType: string) { switch (rawType) { case 'Object': case 'Array': return TargetType.COMMON case 'Map': case 'Set': case 'WeakMap': case 'WeakSet': return TargetType.COLLECTION default: return TargetType.INVALID } } function getTargetType(value: Target) { return value[ReactiveFlags.SKIP] || !Object.isExtensible(value) ? TargetType.INVALID : targetTypeMap(toRawType(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 ) } type Primitive = string | number | boolean | bigint | symbol | undefined | null type Builtin = Primitive | Function | Date | Error | RegExp export type DeepReadonly = T extends Builtin ? T : T extends Map ? ReadonlyMap, DeepReadonly> : T extends ReadonlyMap ? ReadonlyMap, DeepReadonly> : T extends WeakMap ? WeakMap, DeepReadonly> : T extends Set ? ReadonlySet> : T extends ReadonlySet ? ReadonlySet> : T extends WeakSet ? WeakSet> : T extends Promise ? Promise> : T extends {} ? { readonly [K in keyof T]: DeepReadonly } : Readonly export function readonly( target: T ): DeepReadonly> { 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 const reactiveFlag = isReadonly ? ReactiveFlags.READONLY : ReactiveFlags.REACTIVE if (hasOwn(target, reactiveFlag)) { return target[reactiveFlag] } // only a whitelist of value types can be observed. const targetType = getTargetType(target) if (targetType === TargetType.INVALID) { return target } const observed = new Proxy( target, targetType === TargetType.COLLECTION ? collectionHandlers : baseHandlers ) def(target, reactiveFlag, 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 }