import { type IfAny, hasChanged, isArray, isFunction, isIntegerKey, isObject, } from '@vue/shared' import type { ComputedRef, WritableComputedRef } from './computed' import { ReactiveFlags, TrackOpTypes, TriggerOpTypes } from './constants' import { onTrack, triggerEventInfos } from './debug' import { getDepFromReactive } from './dep' import { type Builtin, type ShallowReactiveMarker, type Target, isProxy, isReactive, isReadonly, isShallow, toRaw, toReactive, } from './reactive' import { type Link, type ReactiveNode, ReactiveFlags as _ReactiveFlags, activeSub, batchDepth, flush, link, propagate, shallowPropagate, } from './system' declare const RefSymbol: unique symbol export declare const RawSymbol: unique symbol export interface Ref { get value(): T set value(_: S) /** * Type differentiator only. * We need this to be in public d.ts but don't want it to show up in IDE * autocomplete, so we use a private Symbol instead. */ [RefSymbol]: true } /** * Checks if a value is a ref object. * * @param r - The value to inspect. * @see {@link https://vuejs.org/api/reactivity-utilities.html#isref} */ export function isRef(r: Ref | unknown): r is Ref /*@__NO_SIDE_EFFECTS__*/ export function isRef(r: any): r is Ref { return r ? r[ReactiveFlags.IS_REF] === true : false } /** * Takes an inner value and returns a reactive and mutable ref object, which * has a single property `.value` that points to the inner value. * * @param value - The object to wrap in the ref. * @see {@link https://vuejs.org/api/reactivity-core.html#ref} */ export function ref( value: T, ): [T] extends [Ref] ? IfAny, T> : Ref, UnwrapRef | T> export function ref(): Ref /*@__NO_SIDE_EFFECTS__*/ export function ref(value?: unknown) { return createRef(value, toReactive) } declare const ShallowRefMarker: unique symbol export type ShallowRef = Ref & { [ShallowRefMarker]?: true } /** * Shallow version of {@link ref}. * * @example * ```js * const state = shallowRef({ count: 1 }) * * // does NOT trigger change * state.value.count = 2 * * // does trigger change * state.value = { count: 2 } * ``` * * @param value - The "inner value" for the shallow ref. * @see {@link https://vuejs.org/api/reactivity-advanced.html#shallowref} */ export function shallowRef( value: T, ): Ref extends T ? T extends Ref ? IfAny, T> : ShallowRef : ShallowRef export function shallowRef(): ShallowRef /*@__NO_SIDE_EFFECTS__*/ export function shallowRef(value?: unknown) { return createRef(value) } function createRef(rawValue: unknown, wrap?: (v: T) => T) { if (isRef(rawValue)) { return rawValue } return new RefImpl(rawValue, wrap) } /** * @internal */ class RefImpl implements ReactiveNode { subs: Link | undefined = undefined subsTail: Link | undefined = undefined flags: _ReactiveFlags = _ReactiveFlags.Mutable _value: T _wrap?: (v: T) => T private _oldValue: T private _rawValue: T /** * @internal */ readonly __v_isRef = true // TODO isolatedDeclarations ReactiveFlags.IS_REF /** * @internal */ readonly __v_isShallow: boolean = false // TODO isolatedDeclarations ReactiveFlags.IS_SHALLOW constructor(value: T, wrap: ((v: T) => T) | undefined) { this._oldValue = this._rawValue = wrap ? toRaw(value) : value this._value = wrap ? wrap(value) : value this._wrap = wrap this[ReactiveFlags.IS_SHALLOW] = !wrap } get dep(): this { return this } get value(): T { trackRef(this) if (this.flags & _ReactiveFlags.Dirty && this.update()) { const subs = this.subs if (subs !== undefined) { shallowPropagate(subs) } } return this._value } set value(newValue) { const oldValue = this._rawValue const useDirectValue = this[ReactiveFlags.IS_SHALLOW] || isShallow(newValue) || isReadonly(newValue) newValue = useDirectValue ? newValue : toRaw(newValue) if (hasChanged(newValue, oldValue)) { this.flags |= _ReactiveFlags.Dirty this._rawValue = newValue this._value = !useDirectValue && this._wrap ? this._wrap(newValue) : newValue const subs = this.subs if (subs !== undefined) { if (__DEV__) { triggerEventInfos.push({ target: this, type: TriggerOpTypes.SET, key: 'value', newValue, oldValue, }) } propagate(subs) if (!batchDepth) { flush() } if (__DEV__) { triggerEventInfos.pop() } } } } update(): boolean { this.flags &= ~_ReactiveFlags.Dirty return hasChanged(this._oldValue, (this._oldValue = this._rawValue)) } } /** * Force trigger effects that depends on a shallow ref. This is typically used * after making deep mutations to the inner value of a shallow ref. * * @example * ```js * const shallow = shallowRef({ * greet: 'Hello, world' * }) * * // Logs "Hello, world" once for the first run-through * watchEffect(() => { * console.log(shallow.value.greet) * }) * * // This won't trigger the effect because the ref is shallow * shallow.value.greet = 'Hello, universe' * * // Logs "Hello, universe" * triggerRef(shallow) * ``` * * @param ref - The ref whose tied effects shall be executed. * @see {@link https://vuejs.org/api/reactivity-advanced.html#triggerref} */ export function triggerRef(ref: Ref): void { // ref may be an instance of ObjectRefImpl const dep = (ref as unknown as RefImpl).dep if (dep !== undefined && dep.subs !== undefined) { propagate(dep.subs) shallowPropagate(dep.subs) if (!batchDepth) { flush() } } } function trackRef(dep: ReactiveNode) { if (activeSub !== undefined) { if (__DEV__) { onTrack(activeSub!, { target: dep, type: TrackOpTypes.GET, key: 'value', }) } link(dep, activeSub!) } } export type MaybeRef = | T | Ref | ShallowRef | WritableComputedRef export type MaybeRefOrGetter = MaybeRef | ComputedRef | (() => T) /** * Returns the inner value if the argument is a ref, otherwise return the * argument itself. This is a sugar function for * `val = isRef(val) ? val.value : val`. * * @example * ```js * function useFoo(x: number | Ref) { * const unwrapped = unref(x) * // unwrapped is guaranteed to be number now * } * ``` * * @param ref - Ref or plain value to be converted into the plain value. * @see {@link https://vuejs.org/api/reactivity-utilities.html#unref} */ export function unref(ref: MaybeRef | ComputedRef): T { return isRef(ref) ? ref.value : ref } /** * Normalizes values / refs / getters to values. * This is similar to {@link unref}, except that it also normalizes getters. * If the argument is a getter, it will be invoked and its return value will * be returned. * * @example * ```js * toValue(1) // 1 * toValue(ref(1)) // 1 * toValue(() => 1) // 1 * ``` * * @param source - A getter, an existing ref, or a non-function value. * @see {@link https://vuejs.org/api/reactivity-utilities.html#tovalue} */ export function toValue(source: MaybeRefOrGetter): T { return isFunction(source) ? source() : unref(source) } const shallowUnwrapHandlers: ProxyHandler = { get: (target, key, receiver) => key === ReactiveFlags.RAW ? target : unref(Reflect.get(target, key, receiver)), set: (target, key, value, receiver) => { const oldValue = target[key] if (isRef(oldValue) && !isRef(value)) { oldValue.value = value return true } else { return Reflect.set(target, key, value, receiver) } }, } /** * Returns a proxy for the given object that shallowly unwraps properties that * are refs. If the object already is reactive, it's returned as-is. If not, a * new reactive proxy is created. * * @param objectWithRefs - Either an already-reactive object or a simple object * that contains refs. */ export function proxyRefs( objectWithRefs: T, ): ShallowUnwrapRef { return isReactive(objectWithRefs) ? (objectWithRefs as ShallowUnwrapRef) : new Proxy(objectWithRefs, shallowUnwrapHandlers) } export type CustomRefFactory = ( track: () => void, trigger: () => void, ) => { get: () => T set: (value: T) => void } class CustomRefImpl implements ReactiveNode { subs: Link | undefined = undefined subsTail: Link | undefined = undefined flags: _ReactiveFlags = _ReactiveFlags.None private readonly _get: ReturnType>['get'] private readonly _set: ReturnType>['set'] public readonly [ReactiveFlags.IS_REF] = true public _value: T = undefined! constructor(factory: CustomRefFactory) { const { get, set } = factory( () => trackRef(this), () => triggerRef(this as unknown as Ref), ) this._get = get this._set = set } get dep() { return this } get value() { return (this._value = this._get()) } set value(newVal) { this._set(newVal) } } /** * Creates a customized ref with explicit control over its dependency tracking * and updates triggering. * * @param factory - The function that receives the `track` and `trigger` callbacks. * @see {@link https://vuejs.org/api/reactivity-advanced.html#customref} */ export function customRef(factory: CustomRefFactory): Ref { return new CustomRefImpl(factory) as any } export type ToRefs = { [K in keyof T]: ToRef } /** * Converts a reactive object to a plain object where each property of the * resulting object is a ref pointing to the corresponding property of the * original object. Each individual ref is created using {@link toRef}. * * @param object - Reactive object to be made into an object of linked refs. * @see {@link https://vuejs.org/api/reactivity-utilities.html#torefs} */ /*@__NO_SIDE_EFFECTS__*/ export function toRefs(object: T): ToRefs { const ret: any = isArray(object) ? new Array(object.length) : {} for (const key in object) { ret[key] = propertyToRef(object, key) } return ret } class ObjectRefImpl { public readonly [ReactiveFlags.IS_REF] = true public _value: T[K] = undefined! private readonly _raw: T private readonly _shallow: boolean constructor( private readonly _object: T, private readonly _key: K, private readonly _defaultValue?: T[K], ) { this._raw = toRaw(_object) let shallow = true let obj = _object // For an array with integer key, refs are not unwrapped if (!isArray(_object) || !isIntegerKey(String(_key))) { // Otherwise, check each proxy layer for unwrapping do { shallow = !isProxy(obj) || isShallow(obj) } while (shallow && (obj = (obj as Target)[ReactiveFlags.RAW])) } this._shallow = shallow } get value() { let val = this._object[this._key] if (this._shallow) { val = unref(val) } return (this._value = val === undefined ? this._defaultValue! : val) } set value(newVal) { if (this._shallow && isRef(this._raw[this._key])) { const nestedRef = this._object[this._key] if (isRef(nestedRef)) { nestedRef.value = newVal return } } this._object[this._key] = newVal } get dep(): ReactiveNode | undefined { return getDepFromReactive(this._raw, this._key) } } class GetterRefImpl { public readonly [ReactiveFlags.IS_REF] = true public readonly [ReactiveFlags.IS_READONLY] = true public _value: T = undefined! constructor(private readonly _getter: () => T) {} get value() { return (this._value = this._getter()) } } export type ToRef = IfAny, [T] extends [Ref] ? T : Ref> /** * Used to normalize values / refs / getters into refs. * * @example * ```js * // returns existing refs as-is * toRef(existingRef) * * // creates a ref that calls the getter on .value access * toRef(() => props.foo) * * // creates normal refs from non-function values * // equivalent to ref(1) * toRef(1) * ``` * * Can also be used to create a ref for a property on a source reactive object. * The created ref is synced with its source property: mutating the source * property will update the ref, and vice-versa. * * @example * ```js * const state = reactive({ * foo: 1, * bar: 2 * }) * * const fooRef = toRef(state, 'foo') * * // mutating the ref updates the original * fooRef.value++ * console.log(state.foo) // 2 * * // mutating the original also updates the ref * state.foo++ * console.log(fooRef.value) // 3 * ``` * * @param source - A getter, an existing ref, a non-function value, or a * reactive object to create a property ref from. * @param [key] - (optional) Name of the property in the reactive object. * @see {@link https://vuejs.org/api/reactivity-utilities.html#toref} */ export function toRef( value: T, ): T extends () => infer R ? Readonly> : T extends Ref ? T : Ref> export function toRef( object: T, key: K, ): ToRef export function toRef( object: T, key: K, defaultValue: T[K], ): ToRef> /*@__NO_SIDE_EFFECTS__*/ export function toRef( source: Record | MaybeRef, key?: string, defaultValue?: unknown, ): Ref { if (isRef(source)) { return source } else if (isFunction(source)) { return new GetterRefImpl(source) as any } else if (isObject(source) && arguments.length > 1) { return propertyToRef(source, key!, defaultValue) } else { return ref(source) } } function propertyToRef( source: Record, key: string, defaultValue?: unknown, ) { return new ObjectRefImpl(source, key, defaultValue) as any } /** * This is a special exported interface for other packages to declare * additional types that should bail out for ref unwrapping. For example * \@vue/runtime-dom can declare it like so in its d.ts: * * ``` ts * declare module '@vue/reactivity' { * export interface RefUnwrapBailTypes { * runtimeDOMBailTypes: Node | Window * } * } * ``` */ export interface RefUnwrapBailTypes {} export type ShallowUnwrapRef = { [K in keyof T]: DistributeRef } type DistributeRef = T extends Ref ? V : T export type UnwrapRef = T extends ShallowRef ? V : T extends Ref ? UnwrapRefSimple : UnwrapRefSimple export type UnwrapRefSimple = T extends | Builtin | Ref | RefUnwrapBailTypes[keyof RefUnwrapBailTypes] | { [RawSymbol]?: true } ? T : T extends Map ? Map> & UnwrapRef>> : T extends WeakMap ? WeakMap> & UnwrapRef>> : T extends Set ? Set> & UnwrapRef>> : T extends WeakSet ? WeakSet> & UnwrapRef>> : T extends ReadonlyArray ? { [K in keyof T]: UnwrapRefSimple } : T extends object & { [ShallowReactiveMarker]?: never } ? { [P in keyof T]: P extends symbol ? T[P] : UnwrapRef } : T