254 lines
6.9 KiB
TypeScript
254 lines
6.9 KiB
TypeScript
import { isObject, toRawType, def } from '@vue/shared'
|
|
import {
|
|
mutableHandlers,
|
|
readonlyHandlers,
|
|
shallowReactiveHandlers,
|
|
shallowReadonlyHandlers
|
|
} from './baseHandlers'
|
|
import {
|
|
mutableCollectionHandlers,
|
|
readonlyCollectionHandlers,
|
|
shallowCollectionHandlers,
|
|
shallowReadonlyCollectionHandlers
|
|
} from './collectionHandlers'
|
|
import { UnwrapRefSimple, Ref } from './ref'
|
|
|
|
export const enum ReactiveFlags {
|
|
SKIP = '__v_skip',
|
|
IS_REACTIVE = '__v_isReactive',
|
|
IS_READONLY = '__v_isReadonly',
|
|
IS_SHALLOW = '__v_isShallow',
|
|
RAW = '__v_raw'
|
|
}
|
|
|
|
export interface Target {
|
|
[ReactiveFlags.SKIP]?: boolean
|
|
[ReactiveFlags.IS_REACTIVE]?: boolean
|
|
[ReactiveFlags.IS_READONLY]?: boolean
|
|
[ReactiveFlags.IS_SHALLOW]?: boolean
|
|
[ReactiveFlags.RAW]?: any
|
|
}
|
|
|
|
export const reactiveMap = new WeakMap<Target, any>()
|
|
export const shallowReactiveMap = new WeakMap<Target, any>()
|
|
export const readonlyMap = new WeakMap<Target, any>()
|
|
export const shallowReadonlyMap = new WeakMap<Target, 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
|
|
export type UnwrapNestedRefs<T> = T extends Ref ? T : UnwrapRefSimple<T>
|
|
|
|
/**
|
|
* 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<T extends object>(target: T): UnwrapNestedRefs<T>
|
|
export function reactive(target: object) {
|
|
// if trying to observe a readonly proxy, return the readonly version.
|
|
if (isReadonly(target)) {
|
|
return target
|
|
}
|
|
return createReactiveObject(
|
|
target,
|
|
false,
|
|
mutableHandlers,
|
|
mutableCollectionHandlers,
|
|
reactiveMap
|
|
)
|
|
}
|
|
|
|
export declare const ShallowReactiveMarker: unique symbol
|
|
|
|
export type ShallowReactive<T> = T & { [ShallowReactiveMarker]?: true }
|
|
|
|
/**
|
|
* 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<T extends object>(
|
|
target: T
|
|
): ShallowReactive<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> = T extends Builtin
|
|
? T
|
|
: T extends Map<infer K, infer V>
|
|
? ReadonlyMap<DeepReadonly<K>, DeepReadonly<V>>
|
|
: T extends ReadonlyMap<infer K, infer V>
|
|
? ReadonlyMap<DeepReadonly<K>, DeepReadonly<V>>
|
|
: T extends WeakMap<infer K, infer V>
|
|
? WeakMap<DeepReadonly<K>, DeepReadonly<V>>
|
|
: T extends Set<infer U>
|
|
? ReadonlySet<DeepReadonly<U>>
|
|
: T extends ReadonlySet<infer U>
|
|
? ReadonlySet<DeepReadonly<U>>
|
|
: T extends WeakSet<infer U>
|
|
? WeakSet<DeepReadonly<U>>
|
|
: T extends Promise<infer U>
|
|
? Promise<DeepReadonly<U>>
|
|
: T extends Ref<infer U>
|
|
? Readonly<Ref<DeepReadonly<U>>>
|
|
: T extends {}
|
|
? { readonly [K in keyof T]: DeepReadonly<T[K]> }
|
|
: Readonly<T>
|
|
|
|
/**
|
|
* 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<T extends object>(
|
|
target: T
|
|
): DeepReadonly<UnwrapNestedRefs<T>> {
|
|
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<T extends object>(target: T): Readonly<T> {
|
|
return createReactiveObject(
|
|
target,
|
|
true,
|
|
shallowReadonlyHandlers,
|
|
shallowReadonlyCollectionHandlers,
|
|
shallowReadonlyMap
|
|
)
|
|
}
|
|
|
|
function createReactiveObject(
|
|
target: Target,
|
|
isReadonly: boolean,
|
|
baseHandlers: ProxyHandler<any>,
|
|
collectionHandlers: ProxyHandler<any>,
|
|
proxyMap: WeakMap<Target, 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.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 isShallow(value: unknown): boolean {
|
|
return !!(value && (value as Target)[ReactiveFlags.IS_SHALLOW])
|
|
}
|
|
|
|
export function isProxy(value: unknown): boolean {
|
|
return isReactive(value) || isReadonly(value)
|
|
}
|
|
|
|
export function toRaw<T>(observed: T): T {
|
|
const raw = observed && (observed as Target)[ReactiveFlags.RAW]
|
|
return raw ? toRaw(raw) : observed
|
|
}
|
|
|
|
export function markRaw<T extends object>(value: T): T {
|
|
def(value, ReactiveFlags.SKIP, true)
|
|
return value
|
|
}
|
|
|
|
export const toReactive = <T extends unknown>(value: T): T =>
|
|
isObject(value) ? reactive(value) : value
|
|
|
|
export const toReadonly = <T extends unknown>(value: T): T =>
|
|
isObject(value) ? readonly(value as Record<any, any>) : value
|