import { isTracking, trackEffects, triggerEffects } from './effect' import { TrackOpTypes, TriggerOpTypes } from './operations' import { isArray, isObject, hasChanged } from '@vue/shared' import { reactive, isProxy, toRaw, isReactive } from './reactive' import { CollectionTypes } from './collectionHandlers' import { createDep, Dep } from './dep' declare const RefSymbol: unique symbol export interface Ref { value: T /** * 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 /** * @internal */ _shallow?: boolean } type RefBase = { dep?: Dep value: T } export function trackRefValue(ref: RefBase) { if (isTracking()) { ref = toRaw(ref) if (!ref.dep) { ref.dep = createDep() } if (__DEV__) { trackEffects(ref.dep, { target: ref, type: TrackOpTypes.GET, key: 'value' }) } else { trackEffects(ref.dep) } } } export function triggerRefValue(ref: RefBase, newVal?: any) { ref = toRaw(ref) if (ref.dep) { if (__DEV__) { triggerEffects(ref.dep, { target: ref, type: TriggerOpTypes.SET, key: 'value', newValue: newVal }) } else { triggerEffects(ref.dep) } } } const convert = (val: T): T => isObject(val) ? reactive(val) : val export function isRef(r: Ref | unknown): r is Ref export function isRef(r: any): r is Ref { return Boolean(r && r.__v_isRef === true) } export function ref(value: T): ToRef export function ref(value: T): Ref> export function ref(): Ref export function ref(value?: unknown) { return createRef(value, false) } export function shallowRef( value: T ): T extends Ref ? T : Ref export function shallowRef(value: T): Ref export function shallowRef(): Ref export function shallowRef(value?: unknown) { return createRef(value, true) } class RefImpl { private _value: T private _rawValue: T public dep?: Dep = undefined public readonly __v_isRef = true constructor(value: T, public readonly _shallow: boolean) { this._rawValue = _shallow ? value : toRaw(value) this._value = _shallow ? value : convert(value) } get value() { trackRefValue(this) return this._value } set value(newVal) { newVal = this._shallow ? newVal : toRaw(newVal) if (hasChanged(newVal, this._rawValue)) { this._rawValue = newVal this._value = this._shallow ? newVal : convert(newVal) triggerRefValue(this, newVal) } } } function createRef(rawValue: unknown, shallow: boolean) { if (isRef(rawValue)) { return rawValue } return new RefImpl(rawValue, shallow) } export function triggerRef(ref: Ref) { triggerRefValue(ref, __DEV__ ? ref.value : void 0) } export function unref(ref: T | Ref): T { return isRef(ref) ? (ref.value as any) : ref } const shallowUnwrapHandlers: ProxyHandler = { get: (target, key, receiver) => 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) } } } export function proxyRefs( objectWithRefs: T ): ShallowUnwrapRef { return isReactive(objectWithRefs) ? objectWithRefs : new Proxy(objectWithRefs, shallowUnwrapHandlers) } type CustomRefFactory = ( track: () => void, trigger: () => void ) => { get: () => T set: (value: T) => void } class CustomRefImpl { public dep?: Dep = undefined private readonly _get: ReturnType>['get'] private readonly _set: ReturnType>['set'] public readonly __v_isRef = true constructor(factory: CustomRefFactory) { const { get, set } = factory( () => trackRefValue(this), () => triggerRefValue(this) ) this._get = get this._set = set } get value() { return this._get() } set value(newVal) { this._set(newVal) } } export function customRef(factory: CustomRefFactory): Ref { return new CustomRefImpl(factory) as any } export type ToRefs = { // #2687: somehow using ToRef here turns the resulting type into // a union of multiple Ref<*> types instead of a single Ref<* | *> type. [K in keyof T]: T[K] extends Ref ? T[K] : Ref> } export function toRefs(object: T): ToRefs { if (__DEV__ && !isProxy(object)) { console.warn(`toRefs() expects a reactive object but received a plain one.`) } const ret: any = isArray(object) ? new Array(object.length) : {} for (const key in object) { ret[key] = toRef(object, key) } return ret } class ObjectRefImpl { public readonly __v_isRef = true constructor(private readonly _object: T, private readonly _key: K) {} get value() { return this._object[this._key] } set value(newVal) { this._object[this._key] = newVal } } export type ToRef = [T] extends [Ref] ? T : Ref> export function toRef( object: T, key: K ): ToRef { const val = object[key] return isRef(val) ? val : (new ObjectRefImpl(object, key) as any) } // corner case when use narrows type // Ex. type RelativePath = string & { __brand: unknown } // RelativePath extends object -> true type BaseTypes = string | number | boolean /** * 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 * } * } * ``` * * Note that api-extractor somehow refuses to include `declare module` * augmentations in its generated d.ts, so we have to manually append them * to the final generated d.ts in our build process. */ export interface RefUnwrapBailTypes {} export type ShallowUnwrapRef = { [K in keyof T]: T[K] extends Ref ? V : T[K] extends Ref | undefined // if `V` is `unknown` that means it does not extend `Ref` and is undefined ? unknown extends V ? undefined : V | undefined : T[K] } export type UnwrapRef = T extends Ref ? UnwrapRefSimple : UnwrapRefSimple export type UnwrapRefSimple = T extends | Function | CollectionTypes | BaseTypes | Ref | RefUnwrapBailTypes[keyof RefUnwrapBailTypes] ? T : T extends Array ? { [K in keyof T]: UnwrapRefSimple } : T extends object ? { [P in keyof T]: P extends symbol ? T[P] : UnwrapRef } : T