computed.ts 4.9 KB

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