import { isObject, toRawType, def } from '@vue/shared' import { mutableHandlers, readonlyHandlers, shallowReactiveHandlers, shallowReadonlyHandlers } from './baseHandlers' import { mutableCollectionHandlers, readonlyCollectionHandlers, shallowCollectionHandlers, shallowReadonlyCollectionHandlers } 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' } export interface Target { [ReactiveFlags.SKIP]?: boolean [ReactiveFlags.IS_REACTIVE]?: boolean [ReactiveFlags.IS_READONLY]?: boolean [ReactiveFlags.RAW]?: any } export const reactiveMap = new WeakMap() export const shallowReactiveMap = new WeakMap() export const readonlyMap = new WeakMap() export const shallowReadonlyMap = new WeakMap() 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 export type UnwrapNestedRefs = T extends Ref ? T : UnwrapRef /** * Creates a reactive copy of the original object. * * The reactive conversion is "deep"—it affects all nested properties. In the * ES2015 Proxy based implementation, the returned proxy is **not** equal to the * original object. It is recommended to work exclusively with the reactive * proxy and avoid relying on the original object. * * A reactive object also automatically unwraps refs contained in it, so you * don't need to use `.value` when accessing and mutating their value: * * ```js * const count = ref(0) * const obj = reactive({ * count * }) * * obj.count++ * obj.count // -> 1 * count.value // -> 1 * ``` */ 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, reactiveMap ) } /** * Return a shallowly-reactive copy of the original object, where only the root * level properties are reactive. It also does not auto-unwrap refs (even at the * root level). */ export function shallowReactive(target: T): T { return createReactiveObject( target, false, shallowReactiveHandlers, shallowCollectionHandlers, shallowReactiveMap ) } 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 /** * Creates a readonly copy of the original object. Note the returned copy is not * made reactive, but `readonly` can be called on an already reactive object. */ export function readonly( target: T ): DeepReadonly> { return createReactiveObject( target, true, readonlyHandlers, readonlyCollectionHandlers, readonlyMap ) } /** * Returns 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, shallowReadonlyCollectionHandlers, shallowReadonlyMap ) } function createReactiveObject( target: Target, isReadonly: boolean, baseHandlers: ProxyHandler, collectionHandlers: ProxyHandler, proxyMap: WeakMap ) { 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 existingProxy = proxyMap.get(target) if (existingProxy) { return existingProxy } // only a whitelist of value types can be observed. const targetType = getTargetType(target) if (targetType === TargetType.INVALID) { return target } const proxy = new Proxy( target, targetType === TargetType.COLLECTION ? collectionHandlers : baseHandlers ) proxyMap.set(target, proxy) return proxy } 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 }