ref.ts 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532
  1. import type { ComputedRef } from './computed'
  2. import {
  3. activeEffect,
  4. shouldTrack,
  5. trackEffect,
  6. triggerEffects,
  7. } from './effect'
  8. import { DirtyLevels, TrackOpTypes, TriggerOpTypes } from './constants'
  9. import {
  10. type IfAny,
  11. hasChanged,
  12. isArray,
  13. isFunction,
  14. isObject,
  15. } from '@vue/shared'
  16. import {
  17. isProxy,
  18. isReactive,
  19. isReadonly,
  20. isShallow,
  21. toRaw,
  22. toReactive,
  23. } from './reactive'
  24. import type { Builtin, ShallowReactiveMarker } from './reactive'
  25. import { type Dep, createDep } from './dep'
  26. import { ComputedRefImpl } from './computed'
  27. import { getDepFromReactive } from './reactiveEffect'
  28. import { warn } from './warning'
  29. declare const RefSymbol: unique symbol
  30. export declare const RawSymbol: unique symbol
  31. export interface Ref<T = any> {
  32. value: T
  33. /**
  34. * Type differentiator only.
  35. * We need this to be in public d.ts but don't want it to show up in IDE
  36. * autocomplete, so we use a private Symbol instead.
  37. */
  38. [RefSymbol]: true
  39. }
  40. type RefBase<T> = {
  41. dep?: Dep
  42. value: T
  43. }
  44. export function trackRefValue(ref: RefBase<any>) {
  45. if (shouldTrack && activeEffect) {
  46. ref = toRaw(ref)
  47. trackEffect(
  48. activeEffect,
  49. (ref.dep ??= createDep(
  50. () => (ref.dep = undefined),
  51. ref instanceof ComputedRefImpl ? ref : undefined,
  52. )),
  53. __DEV__
  54. ? {
  55. target: ref,
  56. type: TrackOpTypes.GET,
  57. key: 'value',
  58. }
  59. : void 0,
  60. )
  61. }
  62. }
  63. export function triggerRefValue(
  64. ref: RefBase<any>,
  65. dirtyLevel: DirtyLevels = DirtyLevels.Dirty,
  66. newVal?: any,
  67. oldVal?: any,
  68. ) {
  69. ref = toRaw(ref)
  70. const dep = ref.dep
  71. if (dep) {
  72. triggerEffects(
  73. dep,
  74. dirtyLevel,
  75. __DEV__
  76. ? {
  77. target: ref,
  78. type: TriggerOpTypes.SET,
  79. key: 'value',
  80. newValue: newVal,
  81. oldValue: oldVal,
  82. }
  83. : void 0,
  84. )
  85. }
  86. }
  87. /**
  88. * Checks if a value is a ref object.
  89. *
  90. * @param r - The value to inspect.
  91. * @see {@link https://vuejs.org/api/reactivity-utilities.html#isref}
  92. */
  93. export function isRef<T>(r: Ref<T> | unknown): r is Ref<T>
  94. export function isRef(r: any): r is Ref {
  95. return !!(r && r.__v_isRef === true)
  96. }
  97. /**
  98. * Takes an inner value and returns a reactive and mutable ref object, which
  99. * has a single property `.value` that points to the inner value.
  100. *
  101. * @param value - The object to wrap in the ref.
  102. * @see {@link https://vuejs.org/api/reactivity-core.html#ref}
  103. */
  104. export function ref<T>(value: T): Ref<UnwrapRef<T>>
  105. export function ref<T = any>(): Ref<T | undefined>
  106. export function ref(value?: unknown) {
  107. return createRef(value, false)
  108. }
  109. declare const ShallowRefMarker: unique symbol
  110. export type ShallowRef<T = any> = Ref<T> & { [ShallowRefMarker]?: true }
  111. /**
  112. * Shallow version of {@link ref()}.
  113. *
  114. * @example
  115. * ```js
  116. * const state = shallowRef({ count: 1 })
  117. *
  118. * // does NOT trigger change
  119. * state.value.count = 2
  120. *
  121. * // does trigger change
  122. * state.value = { count: 2 }
  123. * ```
  124. *
  125. * @param value - The "inner value" for the shallow ref.
  126. * @see {@link https://vuejs.org/api/reactivity-advanced.html#shallowref}
  127. */
  128. export function shallowRef<T>(
  129. value: T,
  130. ): Ref extends T
  131. ? T extends Ref
  132. ? IfAny<T, ShallowRef<T>, T>
  133. : ShallowRef<T>
  134. : ShallowRef<T>
  135. export function shallowRef<T = any>(): ShallowRef<T | undefined>
  136. export function shallowRef(value?: unknown) {
  137. return createRef(value, true)
  138. }
  139. function createRef(rawValue: unknown, shallow: boolean) {
  140. if (isRef(rawValue)) {
  141. return rawValue
  142. }
  143. return new RefImpl(rawValue, shallow)
  144. }
  145. class RefImpl<T> {
  146. private _value: T
  147. private _rawValue: T
  148. public dep?: Dep = undefined
  149. public readonly __v_isRef = true
  150. constructor(
  151. value: T,
  152. public readonly __v_isShallow: boolean,
  153. ) {
  154. this._rawValue = __v_isShallow ? value : toRaw(value)
  155. this._value = __v_isShallow ? value : toReactive(value)
  156. }
  157. get value() {
  158. trackRefValue(this)
  159. return this._value
  160. }
  161. set value(newVal) {
  162. const useDirectValue =
  163. this.__v_isShallow || isShallow(newVal) || isReadonly(newVal)
  164. newVal = useDirectValue ? newVal : toRaw(newVal)
  165. if (hasChanged(newVal, this._rawValue)) {
  166. const oldVal = this._rawValue
  167. this._rawValue = newVal
  168. this._value = useDirectValue ? newVal : toReactive(newVal)
  169. triggerRefValue(this, DirtyLevels.Dirty, newVal, oldVal)
  170. }
  171. }
  172. }
  173. /**
  174. * Force trigger effects that depends on a shallow ref. This is typically used
  175. * after making deep mutations to the inner value of a shallow ref.
  176. *
  177. * @example
  178. * ```js
  179. * const shallow = shallowRef({
  180. * greet: 'Hello, world'
  181. * })
  182. *
  183. * // Logs "Hello, world" once for the first run-through
  184. * watchEffect(() => {
  185. * console.log(shallow.value.greet)
  186. * })
  187. *
  188. * // This won't trigger the effect because the ref is shallow
  189. * shallow.value.greet = 'Hello, universe'
  190. *
  191. * // Logs "Hello, universe"
  192. * triggerRef(shallow)
  193. * ```
  194. *
  195. * @param ref - The ref whose tied effects shall be executed.
  196. * @see {@link https://vuejs.org/api/reactivity-advanced.html#triggerref}
  197. */
  198. export function triggerRef(ref: Ref) {
  199. triggerRefValue(ref, DirtyLevels.Dirty, __DEV__ ? ref.value : void 0)
  200. }
  201. export type MaybeRef<T = any> = T | Ref<T>
  202. export type MaybeRefOrGetter<T = any> = MaybeRef<T> | (() => T)
  203. /**
  204. * Returns the inner value if the argument is a ref, otherwise return the
  205. * argument itself. This is a sugar function for
  206. * `val = isRef(val) ? val.value : val`.
  207. *
  208. * @example
  209. * ```js
  210. * function useFoo(x: number | Ref<number>) {
  211. * const unwrapped = unref(x)
  212. * // unwrapped is guaranteed to be number now
  213. * }
  214. * ```
  215. *
  216. * @param ref - Ref or plain value to be converted into the plain value.
  217. * @see {@link https://vuejs.org/api/reactivity-utilities.html#unref}
  218. */
  219. export function unref<T>(ref: MaybeRef<T> | ComputedRef<T> | ShallowRef<T>): T {
  220. return isRef(ref) ? ref.value : ref
  221. }
  222. /**
  223. * Normalizes values / refs / getters to values.
  224. * This is similar to {@link unref()}, except that it also normalizes getters.
  225. * If the argument is a getter, it will be invoked and its return value will
  226. * be returned.
  227. *
  228. * @example
  229. * ```js
  230. * toValue(1) // 1
  231. * toValue(ref(1)) // 1
  232. * toValue(() => 1) // 1
  233. * ```
  234. *
  235. * @param source - A getter, an existing ref, or a non-function value.
  236. * @see {@link https://vuejs.org/api/reactivity-utilities.html#tovalue}
  237. */
  238. export function toValue<T>(
  239. source: MaybeRefOrGetter<T> | ComputedRef<T> | ShallowRef<T>,
  240. ): T {
  241. return isFunction(source) ? source() : unref(source)
  242. }
  243. const shallowUnwrapHandlers: ProxyHandler<any> = {
  244. get: (target, key, receiver) => unref(Reflect.get(target, key, receiver)),
  245. set: (target, key, value, receiver) => {
  246. const oldValue = target[key]
  247. if (isRef(oldValue) && !isRef(value)) {
  248. oldValue.value = value
  249. return true
  250. } else {
  251. return Reflect.set(target, key, value, receiver)
  252. }
  253. },
  254. }
  255. /**
  256. * Returns a reactive proxy for the given object.
  257. *
  258. * If the object already is reactive, it's returned as-is. If not, a new
  259. * reactive proxy is created. Direct child properties that are refs are properly
  260. * handled, as well.
  261. *
  262. * @param objectWithRefs - Either an already-reactive object or a simple object
  263. * that contains refs.
  264. */
  265. export function proxyRefs<T extends object>(
  266. objectWithRefs: T,
  267. ): ShallowUnwrapRef<T> {
  268. return isReactive(objectWithRefs)
  269. ? objectWithRefs
  270. : new Proxy(objectWithRefs, shallowUnwrapHandlers)
  271. }
  272. export type CustomRefFactory<T> = (
  273. track: () => void,
  274. trigger: () => void,
  275. ) => {
  276. get: () => T
  277. set: (value: T) => void
  278. }
  279. class CustomRefImpl<T> {
  280. public dep?: Dep = undefined
  281. private readonly _get: ReturnType<CustomRefFactory<T>>['get']
  282. private readonly _set: ReturnType<CustomRefFactory<T>>['set']
  283. public readonly __v_isRef = true
  284. constructor(factory: CustomRefFactory<T>) {
  285. const { get, set } = factory(
  286. () => trackRefValue(this),
  287. () => triggerRefValue(this),
  288. )
  289. this._get = get
  290. this._set = set
  291. }
  292. get value() {
  293. return this._get()
  294. }
  295. set value(newVal) {
  296. this._set(newVal)
  297. }
  298. }
  299. /**
  300. * Creates a customized ref with explicit control over its dependency tracking
  301. * and updates triggering.
  302. *
  303. * @param factory - The function that receives the `track` and `trigger` callbacks.
  304. * @see {@link https://vuejs.org/api/reactivity-advanced.html#customref}
  305. */
  306. export function customRef<T>(factory: CustomRefFactory<T>): Ref<T> {
  307. return new CustomRefImpl(factory) as any
  308. }
  309. export type ToRefs<T = any> = {
  310. [K in keyof T]: ToRef<T[K]>
  311. }
  312. /**
  313. * Converts a reactive object to a plain object where each property of the
  314. * resulting object is a ref pointing to the corresponding property of the
  315. * original object. Each individual ref is created using {@link toRef()}.
  316. *
  317. * @param object - Reactive object to be made into an object of linked refs.
  318. * @see {@link https://vuejs.org/api/reactivity-utilities.html#torefs}
  319. */
  320. export function toRefs<T extends object>(object: T): ToRefs<T> {
  321. if (__DEV__ && !isProxy(object)) {
  322. warn(`toRefs() expects a reactive object but received a plain one.`)
  323. }
  324. const ret: any = isArray(object) ? new Array(object.length) : {}
  325. for (const key in object) {
  326. ret[key] = propertyToRef(object, key)
  327. }
  328. return ret
  329. }
  330. class ObjectRefImpl<T extends object, K extends keyof T> {
  331. public readonly __v_isRef = true
  332. constructor(
  333. private readonly _object: T,
  334. private readonly _key: K,
  335. private readonly _defaultValue?: T[K],
  336. ) {}
  337. get value() {
  338. const val = this._object[this._key]
  339. return val === undefined ? this._defaultValue! : val
  340. }
  341. set value(newVal) {
  342. this._object[this._key] = newVal
  343. }
  344. get dep(): Dep | undefined {
  345. return getDepFromReactive(toRaw(this._object), this._key)
  346. }
  347. }
  348. class GetterRefImpl<T> {
  349. public readonly __v_isRef = true
  350. public readonly __v_isReadonly = true
  351. constructor(private readonly _getter: () => T) {}
  352. get value() {
  353. return this._getter()
  354. }
  355. }
  356. export type ToRef<T> = IfAny<T, Ref<T>, [T] extends [Ref] ? T : Ref<T>>
  357. /**
  358. * Used to normalize values / refs / getters into refs.
  359. *
  360. * @example
  361. * ```js
  362. * // returns existing refs as-is
  363. * toRef(existingRef)
  364. *
  365. * // creates a ref that calls the getter on .value access
  366. * toRef(() => props.foo)
  367. *
  368. * // creates normal refs from non-function values
  369. * // equivalent to ref(1)
  370. * toRef(1)
  371. * ```
  372. *
  373. * Can also be used to create a ref for a property on a source reactive object.
  374. * The created ref is synced with its source property: mutating the source
  375. * property will update the ref, and vice-versa.
  376. *
  377. * @example
  378. * ```js
  379. * const state = reactive({
  380. * foo: 1,
  381. * bar: 2
  382. * })
  383. *
  384. * const fooRef = toRef(state, 'foo')
  385. *
  386. * // mutating the ref updates the original
  387. * fooRef.value++
  388. * console.log(state.foo) // 2
  389. *
  390. * // mutating the original also updates the ref
  391. * state.foo++
  392. * console.log(fooRef.value) // 3
  393. * ```
  394. *
  395. * @param source - A getter, an existing ref, a non-function value, or a
  396. * reactive object to create a property ref from.
  397. * @param [key] - (optional) Name of the property in the reactive object.
  398. * @see {@link https://vuejs.org/api/reactivity-utilities.html#toref}
  399. */
  400. export function toRef<T>(
  401. value: T,
  402. ): T extends () => infer R
  403. ? Readonly<Ref<R>>
  404. : T extends Ref
  405. ? T
  406. : Ref<UnwrapRef<T>>
  407. export function toRef<T extends object, K extends keyof T>(
  408. object: T,
  409. key: K,
  410. ): ToRef<T[K]>
  411. export function toRef<T extends object, K extends keyof T>(
  412. object: T,
  413. key: K,
  414. defaultValue: T[K],
  415. ): ToRef<Exclude<T[K], undefined>>
  416. export function toRef(
  417. source: Record<string, any> | MaybeRef,
  418. key?: string,
  419. defaultValue?: unknown,
  420. ): Ref {
  421. if (isRef(source)) {
  422. return source
  423. } else if (isFunction(source)) {
  424. return new GetterRefImpl(source) as any
  425. } else if (isObject(source) && arguments.length > 1) {
  426. return propertyToRef(source, key!, defaultValue)
  427. } else {
  428. return ref(source)
  429. }
  430. }
  431. function propertyToRef(
  432. source: Record<string, any>,
  433. key: string,
  434. defaultValue?: unknown,
  435. ) {
  436. const val = source[key]
  437. return isRef(val)
  438. ? val
  439. : (new ObjectRefImpl(source, key, defaultValue) as any)
  440. }
  441. /**
  442. * This is a special exported interface for other packages to declare
  443. * additional types that should bail out for ref unwrapping. For example
  444. * \@vue/runtime-dom can declare it like so in its d.ts:
  445. *
  446. * ``` ts
  447. * declare module '@vue/reactivity' {
  448. * export interface RefUnwrapBailTypes {
  449. * runtimeDOMBailTypes: Node | Window
  450. * }
  451. * }
  452. * ```
  453. */
  454. export interface RefUnwrapBailTypes {}
  455. export type ShallowUnwrapRef<T> = {
  456. [K in keyof T]: DistributeRef<T[K]>
  457. }
  458. type DistributeRef<T> = T extends Ref<infer V> ? V : T
  459. export type UnwrapRef<T> =
  460. T extends ShallowRef<infer V>
  461. ? V
  462. : T extends Ref<infer V>
  463. ? UnwrapRefSimple<V>
  464. : UnwrapRefSimple<T>
  465. export type UnwrapRefSimple<T> = T extends
  466. | Builtin
  467. | Ref
  468. | RefUnwrapBailTypes[keyof RefUnwrapBailTypes]
  469. | { [RawSymbol]?: true }
  470. ? T
  471. : T extends Map<infer K, infer V>
  472. ? Map<K, UnwrapRefSimple<V>> & UnwrapRef<Omit<T, keyof Map<any, any>>>
  473. : T extends WeakMap<infer K, infer V>
  474. ? WeakMap<K, UnwrapRefSimple<V>> &
  475. UnwrapRef<Omit<T, keyof WeakMap<any, any>>>
  476. : T extends Set<infer V>
  477. ? Set<UnwrapRefSimple<V>> & UnwrapRef<Omit<T, keyof Set<any>>>
  478. : T extends WeakSet<infer V>
  479. ? WeakSet<UnwrapRefSimple<V>> & UnwrapRef<Omit<T, keyof WeakSet<any>>>
  480. : T extends ReadonlyArray<any>
  481. ? { [K in keyof T]: UnwrapRefSimple<T[K]> }
  482. : T extends object & { [ShallowReactiveMarker]?: never }
  483. ? {
  484. [P in keyof T]: P extends symbol ? T[P] : UnwrapRef<T[P]>
  485. }
  486. : T