deferredComputed.ts 2.3 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788
  1. import { Dep } from './dep'
  2. import { ReactiveEffect } from './effect'
  3. import { ComputedGetter, ComputedRef } from './computed'
  4. import { ReactiveFlags, toRaw } from './reactive'
  5. import { trackRefValue, triggerRefValue } from './ref'
  6. const tick = /*#__PURE__*/ Promise.resolve()
  7. const queue: any[] = []
  8. let queued = false
  9. const scheduler = (fn: any) => {
  10. queue.push(fn)
  11. if (!queued) {
  12. queued = true
  13. tick.then(flush)
  14. }
  15. }
  16. const flush = () => {
  17. for (let i = 0; i < queue.length; i++) {
  18. queue[i]()
  19. }
  20. queue.length = 0
  21. queued = false
  22. }
  23. class DeferredComputedRefImpl<T> {
  24. public dep?: Dep = undefined
  25. private _value!: T
  26. private _dirty = true
  27. public readonly effect: ReactiveEffect<T>
  28. public readonly __v_isRef = true
  29. public readonly [ReactiveFlags.IS_READONLY] = true
  30. constructor(getter: ComputedGetter<T>) {
  31. let compareTarget: any
  32. let hasCompareTarget = false
  33. let scheduled = false
  34. this.effect = new ReactiveEffect(getter, (computedTrigger?: boolean) => {
  35. if (this.dep) {
  36. if (computedTrigger) {
  37. compareTarget = this._value
  38. hasCompareTarget = true
  39. } else if (!scheduled) {
  40. const valueToCompare = hasCompareTarget ? compareTarget : this._value
  41. scheduled = true
  42. hasCompareTarget = false
  43. scheduler(() => {
  44. if (this.effect.active && this._get() !== valueToCompare) {
  45. triggerRefValue(this)
  46. }
  47. scheduled = false
  48. })
  49. }
  50. // chained upstream computeds are notified synchronously to ensure
  51. // value invalidation in case of sync access; normal effects are
  52. // deferred to be triggered in scheduler.
  53. for (const e of this.dep) {
  54. if (e.computed instanceof DeferredComputedRefImpl) {
  55. e.scheduler!(true /* computedTrigger */)
  56. }
  57. }
  58. }
  59. this._dirty = true
  60. })
  61. this.effect.computed = this as any
  62. }
  63. private _get() {
  64. if (this._dirty) {
  65. this._dirty = false
  66. return (this._value = this.effect.run()!)
  67. }
  68. return this._value
  69. }
  70. get value() {
  71. trackRefValue(this)
  72. // the computed ref may get wrapped by other proxies e.g. readonly() #3376
  73. return toRaw(this)._get()
  74. }
  75. }
  76. export function deferredComputed<T>(getter: () => T): ComputedRef<T> {
  77. return new DeferredComputedRefImpl(getter) as any
  78. }