computed.ts 4.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211
  1. import { isFunction } from '@vue/shared'
  2. import {
  3. type DebuggerEvent,
  4. type DebuggerOptions,
  5. EffectFlags,
  6. type Subscriber,
  7. activeSub,
  8. refreshComputed,
  9. } from './effect'
  10. import type { Ref } from './ref'
  11. import { warn } from './warning'
  12. import { Dep, type Link, globalVersion } from './dep'
  13. import { ReactiveFlags, TrackOpTypes } from './constants'
  14. declare const ComputedRefSymbol: unique symbol
  15. declare const WritableComputedRefSymbol: unique symbol
  16. interface BaseComputedRef<T, S = T> extends Ref<T, S> {
  17. [ComputedRefSymbol]: true
  18. /**
  19. * @deprecated computed no longer uses effect
  20. */
  21. effect: ComputedRefImpl
  22. }
  23. export interface ComputedRef<T = any> extends BaseComputedRef<T> {
  24. readonly value: T
  25. }
  26. export interface WritableComputedRef<T, S = T> extends BaseComputedRef<T, S> {
  27. [WritableComputedRefSymbol]: true
  28. }
  29. export type ComputedGetter<T> = (oldValue?: T) => T
  30. export type ComputedSetter<T> = (newValue: T) => void
  31. export interface WritableComputedOptions<T, S = T> {
  32. get: ComputedGetter<T>
  33. set: ComputedSetter<S>
  34. }
  35. /**
  36. * @private exported by @vue/reactivity for Vue core use, but not exported from
  37. * the main vue package
  38. */
  39. export class ComputedRefImpl<T = any> implements Subscriber {
  40. /**
  41. * @internal
  42. */
  43. _value: any = undefined
  44. /**
  45. * @internal
  46. */
  47. readonly dep: Dep = new Dep(this)
  48. /**
  49. * @internal
  50. */
  51. readonly __v_isRef = true
  52. // TODO isolatedDeclarations ReactiveFlags.IS_REF
  53. /**
  54. * @internal
  55. */
  56. readonly __v_isReadonly: boolean
  57. // TODO isolatedDeclarations ReactiveFlags.IS_READONLY
  58. // A computed is also a subscriber that tracks other deps
  59. /**
  60. * @internal
  61. */
  62. deps?: Link = undefined
  63. /**
  64. * @internal
  65. */
  66. depsTail?: Link = undefined
  67. /**
  68. * @internal
  69. */
  70. flags: EffectFlags = EffectFlags.DIRTY
  71. /**
  72. * @internal
  73. */
  74. globalVersion: number = globalVersion - 1
  75. /**
  76. * @internal
  77. */
  78. isSSR: boolean
  79. // for backwards compat
  80. effect: this = this
  81. // dev only
  82. onTrack?: (event: DebuggerEvent) => void
  83. // dev only
  84. onTrigger?: (event: DebuggerEvent) => void
  85. /**
  86. * Dev only
  87. * @internal
  88. */
  89. _warnRecursive?: boolean
  90. constructor(
  91. public fn: ComputedGetter<T>,
  92. private readonly setter: ComputedSetter<T> | undefined,
  93. isSSR: boolean,
  94. ) {
  95. this[ReactiveFlags.IS_READONLY] = !setter
  96. this.isSSR = isSSR
  97. }
  98. /**
  99. * @internal
  100. */
  101. notify(): void {
  102. this.flags |= EffectFlags.DIRTY
  103. // avoid infinite self recursion
  104. if (activeSub !== this) {
  105. this.dep.notify()
  106. } else if (__DEV__) {
  107. // TODO warn
  108. }
  109. }
  110. get value(): T {
  111. const link = __DEV__
  112. ? this.dep.track({
  113. target: this,
  114. type: TrackOpTypes.GET,
  115. key: 'value',
  116. })
  117. : this.dep.track()
  118. refreshComputed(this)
  119. // sync version after evaluation
  120. if (link) {
  121. link.version = this.dep.version
  122. }
  123. return this._value
  124. }
  125. set value(newValue) {
  126. if (this.setter) {
  127. this.setter(newValue)
  128. } else if (__DEV__) {
  129. warn('Write operation failed: computed value is readonly')
  130. }
  131. }
  132. }
  133. /**
  134. * Takes a getter function and returns a readonly reactive ref object for the
  135. * returned value from the getter. It can also take an object with get and set
  136. * functions to create a writable ref object.
  137. *
  138. * @example
  139. * ```js
  140. * // Creating a readonly computed ref:
  141. * const count = ref(1)
  142. * const plusOne = computed(() => count.value + 1)
  143. *
  144. * console.log(plusOne.value) // 2
  145. * plusOne.value++ // error
  146. * ```
  147. *
  148. * ```js
  149. * // Creating a writable computed ref:
  150. * const count = ref(1)
  151. * const plusOne = computed({
  152. * get: () => count.value + 1,
  153. * set: (val) => {
  154. * count.value = val - 1
  155. * }
  156. * })
  157. *
  158. * plusOne.value = 1
  159. * console.log(count.value) // 0
  160. * ```
  161. *
  162. * @param getter - Function that produces the next value.
  163. * @param debugOptions - For debugging. See {@link https://vuejs.org/guide/extras/reactivity-in-depth.html#computed-debugging}.
  164. * @see {@link https://vuejs.org/api/reactivity-core.html#computed}
  165. */
  166. export function computed<T>(
  167. getter: ComputedGetter<T>,
  168. debugOptions?: DebuggerOptions,
  169. ): ComputedRef<T>
  170. export function computed<T, S = T>(
  171. options: WritableComputedOptions<T, S>,
  172. debugOptions?: DebuggerOptions,
  173. ): WritableComputedRef<T, S>
  174. export function computed<T>(
  175. getterOrOptions: ComputedGetter<T> | WritableComputedOptions<T>,
  176. debugOptions?: DebuggerOptions,
  177. isSSR = false,
  178. ) {
  179. let getter: ComputedGetter<T>
  180. let setter: ComputedSetter<T> | undefined
  181. if (isFunction(getterOrOptions)) {
  182. getter = getterOrOptions
  183. } else {
  184. getter = getterOrOptions.get
  185. setter = getterOrOptions.set
  186. }
  187. const cRef = new ComputedRefImpl(getter, setter, isSSR)
  188. if (__DEV__ && debugOptions && !isSSR) {
  189. cRef.onTrack = debugOptions.onTrack
  190. cRef.onTrigger = debugOptions.onTrigger
  191. }
  192. return cRef as any
  193. }