computed.ts 2.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108
  1. import { DebuggerOptions, ReactiveEffect } from './effect'
  2. import { Ref, trackRefValue, triggerRefValue } from './ref'
  3. import { isFunction, NOOP } from '@vue/shared'
  4. import { ReactiveFlags, toRaw } from './reactive'
  5. import { Dep } from './dep'
  6. declare const ComputedRefSymbol: unique symbol
  7. export interface ComputedRef<T = any> extends WritableComputedRef<T> {
  8. readonly value: T
  9. [ComputedRefSymbol]: true
  10. }
  11. export interface WritableComputedRef<T> extends Ref<T> {
  12. readonly effect: ReactiveEffect<T>
  13. }
  14. export type ComputedGetter<T> = (...args: any[]) => T
  15. export type ComputedSetter<T> = (v: T) => void
  16. export interface WritableComputedOptions<T> {
  17. get: ComputedGetter<T>
  18. set: ComputedSetter<T>
  19. }
  20. export class ComputedRefImpl<T> {
  21. public dep?: Dep = undefined
  22. private _value!: T
  23. public readonly effect: ReactiveEffect<T>
  24. public readonly __v_isRef = true
  25. public readonly [ReactiveFlags.IS_READONLY]: boolean = false
  26. public _dirty = true
  27. public _cacheable: boolean
  28. constructor(
  29. getter: ComputedGetter<T>,
  30. private readonly _setter: ComputedSetter<T>,
  31. isReadonly: boolean,
  32. isSSR: boolean
  33. ) {
  34. this.effect = new ReactiveEffect(getter, () => {
  35. if (!this._dirty) {
  36. this._dirty = true
  37. triggerRefValue(this)
  38. }
  39. })
  40. this.effect.computed = this
  41. this.effect.active = this._cacheable = !isSSR
  42. this[ReactiveFlags.IS_READONLY] = isReadonly
  43. }
  44. get value() {
  45. // the computed ref may get wrapped by other proxies e.g. readonly() #3376
  46. const self = toRaw(this)
  47. trackRefValue(self)
  48. if (self._dirty || !self._cacheable) {
  49. self._dirty = false
  50. self._value = self.effect.run()!
  51. }
  52. return self._value
  53. }
  54. set value(newValue: T) {
  55. this._setter(newValue)
  56. }
  57. }
  58. export function computed<T>(
  59. getter: ComputedGetter<T>,
  60. debugOptions?: DebuggerOptions
  61. ): ComputedRef<T>
  62. export function computed<T>(
  63. options: WritableComputedOptions<T>,
  64. debugOptions?: DebuggerOptions
  65. ): WritableComputedRef<T>
  66. export function computed<T>(
  67. getterOrOptions: ComputedGetter<T> | WritableComputedOptions<T>,
  68. debugOptions?: DebuggerOptions,
  69. isSSR = false
  70. ) {
  71. let getter: ComputedGetter<T>
  72. let setter: ComputedSetter<T>
  73. const onlyGetter = isFunction(getterOrOptions)
  74. if (onlyGetter) {
  75. getter = getterOrOptions
  76. setter = __DEV__
  77. ? () => {
  78. console.warn('Write operation failed: computed value is readonly')
  79. }
  80. : NOOP
  81. } else {
  82. getter = getterOrOptions.get
  83. setter = getterOrOptions.set
  84. }
  85. const cRef = new ComputedRefImpl(getter, setter, onlyGetter || !setter, isSSR)
  86. if (__DEV__ && debugOptions && !isSSR) {
  87. cRef.effect.onTrack = debugOptions.onTrack
  88. cRef.effect.onTrigger = debugOptions.onTrigger
  89. }
  90. return cRef as any
  91. }