import { isObject } from '@vue/shared' import { mutableHandlers, readonlyHandlers } from './baseHandlers' import { mutableCollectionHandlers, readonlyCollectionHandlers } from './collectionHandlers' import { UnwrapNestedRefs } from './ref' import { ReactiveEffect } from './effect' // The main WeakMap that stores {target -> key -> dep} connections. // Conceptually, it's easier to think of a dependency as a Dep class // which maintains a Set of subscribers, but we simply store them as // raw Sets to reduce memory overhead. export type Dep = Set export type KeyToDepMap = Map export const targetMap: WeakMap = new WeakMap() // WeakMaps that store {raw <-> observed} pairs. const rawToReactive: WeakMap = new WeakMap() const reactiveToRaw: WeakMap = new WeakMap() const rawToReadonly: WeakMap = new WeakMap() const readonlyToRaw: WeakMap = new WeakMap() // WeakSets for values that are marked readonly or non-reactive during // observable creation. const readonlyValues: WeakSet = new WeakSet() const nonReactiveValues: WeakSet = new WeakSet() const collectionTypes: Set = new Set([Set, Map, WeakMap, WeakSet]) const observableValueRE = /^\[object (?:Object|Array|Map|Set|WeakMap|WeakSet)\]$/ const canObserve = (value: any): boolean => { return ( !value._isVue && !value._isVNode && observableValueRE.test(Object.prototype.toString.call(value)) && !nonReactiveValues.has(value) ) } export function reactive(target: T): UnwrapNestedRefs export function reactive(target: object) { // if trying to observe a readonly proxy, return the readonly version. if (readonlyToRaw.has(target)) { return target } // target is explicitly marked as readonly by user if (readonlyValues.has(target)) { return readonly(target) } return createReactiveObject( target, rawToReactive, reactiveToRaw, mutableHandlers, mutableCollectionHandlers ) } export function readonly( target: T ): Readonly> export function readonly(target: object) { // value is a mutable observable, retrive its original and return // a readonly version. if (reactiveToRaw.has(target)) { target = reactiveToRaw.get(target) } return createReactiveObject( target, rawToReadonly, readonlyToRaw, readonlyHandlers, readonlyCollectionHandlers ) } function createReactiveObject( target: any, toProxy: WeakMap, toRaw: WeakMap, baseHandlers: ProxyHandler, collectionHandlers: ProxyHandler ) { if (!isObject(target)) { if (__DEV__) { console.warn(`value cannot be made reactive: ${String(target)}`) } return target } // target already has corresponding Proxy let observed = toProxy.get(target) if (observed !== void 0) { return observed } // target is already a Proxy if (toRaw.has(target)) { return target } // only a whitelist of value types can be observed. if (!canObserve(target)) { return target } const handlers = collectionTypes.has(target.constructor) ? collectionHandlers : baseHandlers observed = new Proxy(target, handlers) toProxy.set(target, observed) toRaw.set(observed, target) if (!targetMap.has(target)) { targetMap.set(target, new Map()) } return observed } export function isReactive(value: any): boolean { return reactiveToRaw.has(value) || readonlyToRaw.has(value) } export function isReadonly(value: any): boolean { return readonlyToRaw.has(value) } export function toRaw(observed: T): T { return reactiveToRaw.get(observed) || readonlyToRaw.get(observed) || observed } export function markReadonly(value: T): T { readonlyValues.add(value) return value } export function markNonReactive(value: T): T { nonReactiveValues.add(value) return value }