computed.ts 4.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212
  1. import { isFunction } from '@vue/shared'
  2. import {
  3. type DebuggerEvent,
  4. type DebuggerOptions,
  5. EffectFlags,
  6. type Link,
  7. type Subscriber,
  8. activeSub,
  9. refreshComputed,
  10. } from './effect'
  11. import type { Ref } from './ref'
  12. import { warn } from './warning'
  13. import { Dep, 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(): void {
  103. this.flags |= EffectFlags.DIRTY
  104. // avoid infinite self recursion
  105. if (activeSub !== this) {
  106. this.dep.notify()
  107. } else if (__DEV__) {
  108. // TODO warn
  109. }
  110. }
  111. get value(): T {
  112. const link = __DEV__
  113. ? this.dep.track({
  114. target: this,
  115. type: TrackOpTypes.GET,
  116. key: 'value',
  117. })
  118. : this.dep.track()
  119. refreshComputed(this)
  120. // sync version after evaluation
  121. if (link) {
  122. link.version = this.dep.version
  123. }
  124. return this._value
  125. }
  126. set value(newValue) {
  127. if (this.setter) {
  128. this.setter(newValue)
  129. } else if (__DEV__) {
  130. warn('Write operation failed: computed value is readonly')
  131. }
  132. }
  133. }
  134. /**
  135. * Takes a getter function and returns a readonly reactive ref object for the
  136. * returned value from the getter. It can also take an object with get and set
  137. * functions to create a writable ref object.
  138. *
  139. * @example
  140. * ```js
  141. * // Creating a readonly computed ref:
  142. * const count = ref(1)
  143. * const plusOne = computed(() => count.value + 1)
  144. *
  145. * console.log(plusOne.value) // 2
  146. * plusOne.value++ // error
  147. * ```
  148. *
  149. * ```js
  150. * // Creating a writable computed ref:
  151. * const count = ref(1)
  152. * const plusOne = computed({
  153. * get: () => count.value + 1,
  154. * set: (val) => {
  155. * count.value = val - 1
  156. * }
  157. * })
  158. *
  159. * plusOne.value = 1
  160. * console.log(count.value) // 0
  161. * ```
  162. *
  163. * @param getter - Function that produces the next value.
  164. * @param debugOptions - For debugging. See {@link https://vuejs.org/guide/extras/reactivity-in-depth.html#computed-debugging}.
  165. * @see {@link https://vuejs.org/api/reactivity-core.html#computed}
  166. */
  167. export function computed<T>(
  168. getter: ComputedGetter<T>,
  169. debugOptions?: DebuggerOptions,
  170. ): ComputedRef<T>
  171. export function computed<T, S = T>(
  172. options: WritableComputedOptions<T, S>,
  173. debugOptions?: DebuggerOptions,
  174. ): WritableComputedRef<T, S>
  175. export function computed<T>(
  176. getterOrOptions: ComputedGetter<T> | WritableComputedOptions<T>,
  177. debugOptions?: DebuggerOptions,
  178. isSSR = false,
  179. ) {
  180. let getter: ComputedGetter<T>
  181. let setter: ComputedSetter<T> | undefined
  182. if (isFunction(getterOrOptions)) {
  183. getter = getterOrOptions
  184. } else {
  185. getter = getterOrOptions.get
  186. setter = getterOrOptions.set
  187. }
  188. const cRef = new ComputedRefImpl(getter, setter, isSSR)
  189. if (__DEV__ && debugOptions && !isSSR) {
  190. cRef.onTrack = debugOptions.onTrack
  191. cRef.onTrigger = debugOptions.onTrigger
  192. }
  193. return cRef as any
  194. }