| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426 |
- import { def, isObject, toRawType } from '@vue/shared'
- import {
- mutableHandlers,
- readonlyHandlers,
- shallowReactiveHandlers,
- shallowReadonlyHandlers,
- } from './baseHandlers'
- import {
- mutableCollectionHandlers,
- readonlyCollectionHandlers,
- shallowCollectionHandlers,
- shallowReadonlyCollectionHandlers,
- } from './collectionHandlers'
- import type { RawSymbol, Ref, UnwrapRefSimple } from './ref'
- import { ReactiveFlags } from './constants'
- import { warn } from './warning'
- 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>()
- 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>
- declare const ReactiveMarkerSymbol: unique symbol
- export declare class ReactiveMarker {
- private [ReactiveMarkerSymbol]?: void
- }
- export type Reactive<T> = UnwrapNestedRefs<T> &
- (T extends readonly any[] ? ReactiveMarker : {})
- /**
- * Returns a reactive proxy of the object.
- *
- * The reactive conversion is "deep": it affects all nested properties. A
- * reactive object also deeply unwraps any properties that are refs while
- * maintaining reactivity.
- *
- * @example
- * ```js
- * const obj = reactive({ count: 0 })
- * ```
- *
- * @param target - The source object.
- * @see {@link https://vuejs.org/api/reactivity-core.html#reactive}
- */
- export function reactive<T extends object>(target: T): Reactive<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 }
- /**
- * Shallow version of {@link reactive()}.
- *
- * Unlike {@link reactive()}, there is no deep conversion: only root-level
- * properties are reactive for a shallow reactive object. Property values are
- * stored and exposed as-is - this also means properties with ref values will
- * not be automatically unwrapped.
- *
- * @example
- * ```js
- * const state = shallowReactive({
- * foo: 1,
- * nested: {
- * bar: 2
- * }
- * })
- *
- * // mutating state's own properties is reactive
- * state.foo++
- *
- * // ...but does not convert nested objects
- * isReactive(state.nested) // false
- *
- * // NOT reactive
- * state.nested.bar++
- * ```
- *
- * @param target - The source object.
- * @see {@link https://vuejs.org/api/reactivity-advanced.html#shallowreactive}
- */
- 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
- export 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>
- /**
- * Takes an object (reactive or plain) or a ref and returns a readonly proxy to
- * the original.
- *
- * A readonly proxy is deep: any nested property accessed will be readonly as
- * well. It also has the same ref-unwrapping behavior as {@link reactive()},
- * except the unwrapped values will also be made readonly.
- *
- * @example
- * ```js
- * const original = reactive({ count: 0 })
- *
- * const copy = readonly(original)
- *
- * watchEffect(() => {
- * // works for reactivity tracking
- * console.log(copy.count)
- * })
- *
- * // mutating original will trigger watchers relying on the copy
- * original.count++
- *
- * // mutating the copy will fail and result in a warning
- * copy.count++ // warning!
- * ```
- *
- * @param target - The source object.
- * @see {@link https://vuejs.org/api/reactivity-core.html#readonly}
- */
- export function readonly<T extends object>(
- target: T,
- ): DeepReadonly<UnwrapNestedRefs<T>> {
- return createReactiveObject(
- target,
- true,
- readonlyHandlers,
- readonlyCollectionHandlers,
- readonlyMap,
- )
- }
- /**
- * Shallow version of {@link readonly()}.
- *
- * Unlike {@link readonly()}, there is no deep conversion: only root-level
- * properties are made readonly. Property values are stored and exposed as-is -
- * this also means properties with ref values will not be automatically
- * unwrapped.
- *
- * @example
- * ```js
- * const state = shallowReadonly({
- * foo: 1,
- * nested: {
- * bar: 2
- * }
- * })
- *
- * // mutating state's own properties will fail
- * state.foo++
- *
- * // ...but works on nested objects
- * isReadonly(state.nested) // false
- *
- * // works
- * state.nested.bar++
- * ```
- *
- * @param target - The source object.
- * @see {@link https://vuejs.org/api/reactivity-advanced.html#shallowreadonly}
- */
- 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__) {
- warn(
- `value cannot be made ${isReadonly ? 'readonly' : '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 specific 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
- }
- /**
- * Checks if an object is a proxy created by {@link reactive()} or
- * {@link shallowReactive()} (or {@link ref()} in some cases).
- *
- * @example
- * ```js
- * isReactive(reactive({})) // => true
- * isReactive(readonly(reactive({}))) // => true
- * isReactive(ref({}).value) // => true
- * isReactive(readonly(ref({})).value) // => true
- * isReactive(ref(true)) // => false
- * isReactive(shallowRef({}).value) // => false
- * isReactive(shallowReactive({})) // => true
- * ```
- *
- * @param value - The value to check.
- * @see {@link https://vuejs.org/api/reactivity-utilities.html#isreactive}
- */
- export function isReactive(value: unknown): boolean {
- if (isReadonly(value)) {
- return isReactive((value as Target)[ReactiveFlags.RAW])
- }
- return !!(value && (value as Target)[ReactiveFlags.IS_REACTIVE])
- }
- /**
- * Checks whether the passed value is a readonly object. The properties of a
- * readonly object can change, but they can't be assigned directly via the
- * passed object.
- *
- * The proxies created by {@link readonly()} and {@link shallowReadonly()} are
- * both considered readonly, as is a computed ref without a set function.
- *
- * @param value - The value to check.
- * @see {@link https://vuejs.org/api/reactivity-utilities.html#isreadonly}
- */
- 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])
- }
- /**
- * Checks if an object is a proxy created by {@link reactive},
- * {@link readonly}, {@link shallowReactive} or {@link shallowReadonly()}.
- *
- * @param value - The value to check.
- * @see {@link https://vuejs.org/api/reactivity-utilities.html#isproxy}
- */
- export function isProxy(value: any): boolean {
- return value ? !!value[ReactiveFlags.RAW] : false
- }
- /**
- * Returns the raw, original object of a Vue-created proxy.
- *
- * `toRaw()` can return the original object from proxies created by
- * {@link reactive()}, {@link readonly()}, {@link shallowReactive()} or
- * {@link shallowReadonly()}.
- *
- * This is an escape hatch that can be used to temporarily read without
- * incurring proxy access / tracking overhead or write without triggering
- * changes. It is **not** recommended to hold a persistent reference to the
- * original object. Use with caution.
- *
- * @example
- * ```js
- * const foo = {}
- * const reactiveFoo = reactive(foo)
- *
- * console.log(toRaw(reactiveFoo) === foo) // true
- * ```
- *
- * @param observed - The object for which the "raw" value is requested.
- * @see {@link https://vuejs.org/api/reactivity-advanced.html#toraw}
- */
- export function toRaw<T>(observed: T): T {
- const raw = observed && (observed as Target)[ReactiveFlags.RAW]
- return raw ? toRaw(raw) : observed
- }
- export type Raw<T> = T & { [RawSymbol]?: true }
- /**
- * Marks an object so that it will never be converted to a proxy. Returns the
- * object itself.
- *
- * @example
- * ```js
- * const foo = markRaw({})
- * console.log(isReactive(reactive(foo))) // false
- *
- * // also works when nested inside other reactive objects
- * const bar = reactive({ foo })
- * console.log(isReactive(bar.foo)) // false
- * ```
- *
- * **Warning:** `markRaw()` together with the shallow APIs such as
- * {@link shallowReactive()} allow you to selectively opt-out of the default
- * deep reactive/readonly conversion and embed raw, non-proxied objects in your
- * state graph.
- *
- * @param value - The object to be marked as "raw".
- * @see {@link https://vuejs.org/api/reactivity-advanced.html#markraw}
- */
- export function markRaw<T extends object>(value: T): Raw<T> {
- if (Object.isExtensible(value)) {
- def(value, ReactiveFlags.SKIP, true)
- }
- return value
- }
- /**
- * Returns a reactive proxy of the given value (if possible).
- *
- * If the given value is not an object, the original value itself is returned.
- *
- * @param value - The value for which a reactive proxy shall be created.
- */
- export const toReactive = <T extends unknown>(value: T): T =>
- isObject(value) ? reactive(value) : value
- /**
- * Returns a readonly proxy of the given value (if possible).
- *
- * If the given value is not an object, the original value itself is returned.
- *
- * @param value - The value for which a readonly proxy shall be created.
- */
- export const toReadonly = <T extends unknown>(value: T): DeepReadonly<T> =>
- isObject(value) ? readonly(value) : (value as DeepReadonly<T>)
|