vue3-yuanma/packages/reactivity/src/reactive.ts

163 lines
4.5 KiB
TypeScript
Raw Normal View History

import { isObject, toRawType } from '@vue/shared'
import {
mutableHandlers,
readonlyHandlers,
readonlyPropsHandlers
} from './baseHandlers'
2019-06-12 00:03:50 +08:00
import {
mutableCollectionHandlers,
2019-08-23 21:38:32 +08:00
readonlyCollectionHandlers
2019-06-12 00:03:50 +08:00
} from './collectionHandlers'
2018-11-14 00:03:35 +08:00
import { ReactiveEffect } from './effect'
2019-10-14 23:21:09 +08:00
import { UnwrapRef, Ref } from './ref'
import { makeMap } from '@vue/shared'
2018-09-19 23:35:38 +08:00
// 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.
2018-11-14 00:03:35 +08:00
export type Dep = Set<ReactiveEffect>
2019-10-22 23:26:48 +08:00
export type KeyToDepMap = Map<any, Dep>
export const targetMap = new WeakMap<any, KeyToDepMap>()
2018-09-19 23:35:38 +08:00
// WeakMaps that store {raw <-> observed} pairs.
const rawToReactive = new WeakMap<any, any>()
const reactiveToRaw = new WeakMap<any, any>()
const rawToReadonly = new WeakMap<any, any>()
const readonlyToRaw = new WeakMap<any, any>()
2018-09-19 23:35:38 +08:00
2019-08-23 21:38:32 +08:00
// WeakSets for values that are marked readonly or non-reactive during
2018-09-19 23:35:38 +08:00
// observable creation.
const readonlyValues = new WeakSet<any>()
const nonReactiveValues = new WeakSet<any>()
2019-06-12 00:03:50 +08:00
const collectionTypes = new Set<Function>([Set, Map, WeakMap, WeakSet])
const isObservableType = /*#__PURE__*/ makeMap(
'Object,Array,Map,Set,WeakMap,WeakSet'
)
2019-06-12 00:03:50 +08:00
const canObserve = (value: any): boolean => {
return (
!value._isVue &&
!value._isVNode &&
isObservableType(toRawType(value)) &&
2019-06-12 00:03:50 +08:00
!nonReactiveValues.has(value)
)
}
2019-10-14 23:21:09 +08:00
// only unwrap nested ref
type UnwrapNestedRefs<T> = T extends Ref ? T : UnwrapRef<T>
export function reactive<T extends object>(target: T): UnwrapNestedRefs<T>
export function reactive(target: object) {
2019-08-23 21:38:32 +08:00
// if trying to observe a readonly proxy, return the readonly version.
if (readonlyToRaw.has(target)) {
2019-06-12 00:03:50 +08:00
return target
}
2019-08-23 21:38:32 +08:00
// target is explicitly marked as readonly by user
if (readonlyValues.has(target)) {
return readonly(target)
2019-06-12 00:03:50 +08:00
}
2019-08-20 21:58:10 +08:00
return createReactiveObject(
2019-06-12 00:03:50 +08:00
target,
2019-08-23 21:38:32 +08:00
rawToReactive,
reactiveToRaw,
2019-06-12 00:03:50 +08:00
mutableHandlers,
mutableCollectionHandlers
)
}
2019-06-12 00:03:50 +08:00
2019-08-23 21:38:32 +08:00
export function readonly<T extends object>(
target: T
2019-10-10 02:01:53 +08:00
): Readonly<UnwrapNestedRefs<T>> {
2019-10-06 03:39:47 +08:00
// value is a mutable observable, retrieve its original and return
2019-06-12 00:03:50 +08:00
// a readonly version.
2019-08-23 21:38:32 +08:00
if (reactiveToRaw.has(target)) {
target = reactiveToRaw.get(target)
2019-06-12 00:03:50 +08:00
}
2019-08-20 21:58:10 +08:00
return createReactiveObject(
2019-06-12 00:03:50 +08:00
target,
2019-08-23 21:38:32 +08:00
rawToReadonly,
readonlyToRaw,
readonlyHandlers,
readonlyCollectionHandlers
2019-06-12 00:03:50 +08:00
)
}
2019-06-12 00:03:50 +08:00
// @internal
// Return a readonly-copy of a props object, without unwrapping refs at the root
// level. This is intended to allow explicitly passing refs as props.
// Technically this should use different global cache from readonly(), but
// since it is only used on internal objects so it's not really necessary.
export function readonlyProps<T extends object>(
target: T
): Readonly<{ [K in keyof T]: UnwrapNestedRefs<T[K]> }> {
return createReactiveObject(
target,
rawToReadonly,
readonlyToRaw,
readonlyPropsHandlers,
readonlyCollectionHandlers
)
}
2019-08-20 21:58:10 +08:00
function createReactiveObject(
2019-10-22 23:26:48 +08:00
target: unknown,
2019-06-12 00:03:50 +08:00
toProxy: WeakMap<any, any>,
toRaw: WeakMap<any, any>,
baseHandlers: ProxyHandler<any>,
collectionHandlers: ProxyHandler<any>
) {
if (!isObject(target)) {
if (__DEV__) {
2019-08-20 21:58:10 +08:00
console.warn(`value cannot be made reactive: ${String(target)}`)
2019-06-12 00:03:50 +08:00
}
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
}
2019-10-22 23:26:48 +08:00
export function isReactive(value: unknown): boolean {
2019-08-23 21:38:32 +08:00
return reactiveToRaw.has(value) || readonlyToRaw.has(value)
2019-06-12 00:03:50 +08:00
}
2019-10-22 23:26:48 +08:00
export function isReadonly(value: unknown): boolean {
2019-08-23 21:38:32 +08:00
return readonlyToRaw.has(value)
2019-06-12 00:03:50 +08:00
}
export function toRaw<T>(observed: T): T {
2019-08-23 21:38:32 +08:00
return reactiveToRaw.get(observed) || readonlyToRaw.get(observed) || observed
2019-06-12 00:03:50 +08:00
}
2019-08-23 21:38:32 +08:00
export function markReadonly<T>(value: T): T {
readonlyValues.add(value)
2019-06-12 00:03:50 +08:00
return value
}
export function markNonReactive<T>(value: T): T {
nonReactiveValues.add(value)
return value
}