ref.ts 15 KB

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