170 lines
4.3 KiB
TypeScript

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',
isReactive = '__v_isReactive',
isReadonly = '__v_isReadonly',
raw = '__v_raw',
reactive = '__v_reactive',
readonly = '__v_readonly'
}
interface Target {
__v_skip?: boolean
__v_isReactive?: boolean
__v_isReadonly?: boolean
__v_raw?: any
__v_reactive?: any
__v_readonly?: any
}
const collectionTypes = new Set<Function>([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> = T extends Ref ? T : UnwrapRef<T>
export function reactive<T extends object>(target: T): UnwrapNestedRefs<T>
export function reactive(target: object) {
// if trying to observe a readonly proxy, return the readonly version.
if (target && (target as Target)[ReactiveFlags.isReadonly]) {
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<T extends object>(target: T): T {
return createReactiveObject(
target,
false,
shallowReactiveHandlers,
shallowCollectionHandlers
)
}
export function readonly<T extends object>(
target: T
): Readonly<UnwrapNestedRefs<T>> {
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<T extends object>(
target: T
): Readonly<{ [K in keyof T]: UnwrapNestedRefs<T[K]> }> {
return createReactiveObject(
target,
true,
shallowReadonlyHandlers,
readonlyCollectionHandlers
)
}
function createReactiveObject(
target: Target,
isReadonly: boolean,
baseHandlers: ProxyHandler<any>,
collectionHandlers: ProxyHandler<any>
) {
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.isReactive])
) {
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.isReactive])
}
export function isReadonly(value: unknown): boolean {
return !!(value && (value as Target)[ReactiveFlags.isReadonly])
}
export function isProxy(value: unknown): boolean {
return isReactive(value) || isReadonly(value)
}
export function toRaw<T>(observed: T): T {
return (
(observed && toRaw((observed as Target)[ReactiveFlags.raw])) || observed
)
}
export function markRaw<T extends object>(value: T): T {
def(value, ReactiveFlags.skip, true)
return value
}