2
0

computed.ts 3.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125
  1. import { ReactiveEffect } from './effect'
  2. import { Ref, trackRefValue, triggerRefValue } from './ref'
  3. import { isFunction, NOOP } from '@vue/shared'
  4. import { ReactiveFlags, toRaw } from './reactive'
  5. export interface ComputedRef<T = any> extends WritableComputedRef<T> {
  6. readonly value: T
  7. defer?: (fn: () => void) => void
  8. }
  9. export interface WritableComputedRef<T> extends Ref<T> {
  10. readonly effect: ReactiveEffect<T>
  11. }
  12. export type ComputedGetter<T> = (ctx?: any) => T
  13. export type ComputedSetter<T> = (v: T) => void
  14. export interface WritableComputedOptions<T> {
  15. get: ComputedGetter<T>
  16. set: ComputedSetter<T>
  17. }
  18. type ComputedScheduler = (fn: () => void) => void
  19. let scheduler: ComputedScheduler | undefined
  20. /**
  21. * Set a scheduler for deferring computed computations
  22. */
  23. export const setComputedScheduler = (s: ComputedScheduler | undefined) => {
  24. scheduler = s
  25. }
  26. class ComputedRefImpl<T> {
  27. public dep?: Set<ReactiveEffect> = undefined
  28. private _value!: T
  29. private _dirty = true
  30. public readonly effect: ReactiveEffect<T>
  31. public readonly __v_isRef = true;
  32. public readonly [ReactiveFlags.IS_READONLY]: boolean
  33. constructor(
  34. getter: ComputedGetter<T>,
  35. private readonly _setter: ComputedSetter<T>,
  36. isReadonly: boolean
  37. ) {
  38. let deferFn: () => void
  39. let scheduled = false
  40. this.effect = new ReactiveEffect(getter, () => {
  41. if (!this._dirty) {
  42. this._dirty = true
  43. if (scheduler) {
  44. if (!scheduled) {
  45. scheduled = true
  46. scheduler(
  47. deferFn ||
  48. (deferFn = () => {
  49. scheduled = false
  50. if (this._dirty) {
  51. this._dirty = false
  52. const newValue = this.effect.run()!
  53. if (this._value !== newValue) {
  54. this._value = newValue
  55. triggerRefValue(this)
  56. }
  57. } else {
  58. triggerRefValue(this)
  59. }
  60. })
  61. )
  62. }
  63. } else {
  64. triggerRefValue(this)
  65. }
  66. }
  67. })
  68. this[ReactiveFlags.IS_READONLY] = isReadonly
  69. }
  70. get value() {
  71. // the computed ref may get wrapped by other proxies e.g. readonly() #3376
  72. const self = toRaw(this)
  73. if (self._dirty) {
  74. self._value = self.effect.run()!
  75. self._dirty = false
  76. }
  77. trackRefValue(self)
  78. return self._value
  79. }
  80. set value(newValue: T) {
  81. this._setter(newValue)
  82. }
  83. }
  84. export function computed<T>(getter: ComputedGetter<T>): ComputedRef<T>
  85. export function computed<T>(
  86. options: WritableComputedOptions<T>
  87. ): WritableComputedRef<T>
  88. export function computed<T>(
  89. getterOrOptions: ComputedGetter<T> | WritableComputedOptions<T>
  90. ) {
  91. let getter: ComputedGetter<T>
  92. let setter: ComputedSetter<T>
  93. if (isFunction(getterOrOptions)) {
  94. getter = getterOrOptions
  95. setter = __DEV__
  96. ? () => {
  97. console.warn('Write operation failed: computed value is readonly')
  98. }
  99. : NOOP
  100. } else {
  101. getter = getterOrOptions.get
  102. setter = getterOrOptions.set
  103. }
  104. return new ComputedRefImpl(
  105. getter,
  106. setter,
  107. isFunction(getterOrOptions) || !getterOrOptions.set
  108. ) as any
  109. }