computed.ts 2.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475
  1. import { effect, ReactiveEffect, activeReactiveEffectStack } from './effect'
  2. import { UnwrapNestedRefs, knownRefs, Ref } from './ref'
  3. import { isFunction } from '@vue/shared'
  4. export interface ComputedRef<T> {
  5. readonly value: UnwrapNestedRefs<T>
  6. readonly effect: ReactiveEffect
  7. }
  8. export interface ComputedOptions<T = any> {
  9. get: () => T
  10. set: (v: T) => void
  11. }
  12. export function computed<T>(getter: () => T): ComputedRef<T>
  13. export function computed<T>(options: ComputedOptions<T>): Ref<T>
  14. export function computed<T>(
  15. getterOrOptions: (() => T) | ComputedOptions<T>
  16. ): Ref<T> {
  17. const isReadonly = isFunction(getterOrOptions)
  18. const getter = isReadonly
  19. ? (getterOrOptions as (() => T))
  20. : (getterOrOptions as ComputedOptions<T>).get
  21. const setter = isReadonly ? null : (getterOrOptions as ComputedOptions<T>).set
  22. let dirty: boolean = true
  23. let value: any = undefined
  24. const runner = effect(getter, {
  25. lazy: true,
  26. // mark effect as computed so that it gets priority during trigger
  27. computed: true,
  28. scheduler: () => {
  29. dirty = true
  30. }
  31. })
  32. const computedValue = {
  33. // expose effect so computed can be stopped
  34. effect: runner,
  35. get value() {
  36. if (dirty) {
  37. value = runner()
  38. dirty = false
  39. }
  40. // When computed effects are accessed in a parent effect, the parent
  41. // should track all the dependencies the computed property has tracked.
  42. // This should also apply for chained computed properties.
  43. trackChildRun(runner)
  44. return value
  45. },
  46. set value(newValue) {
  47. if (setter) {
  48. setter(newValue)
  49. } else {
  50. // TODO warn attempting to mutate readonly computed value
  51. }
  52. }
  53. }
  54. knownRefs.add(computedValue)
  55. return computedValue
  56. }
  57. function trackChildRun(childRunner: ReactiveEffect) {
  58. const parentRunner =
  59. activeReactiveEffectStack[activeReactiveEffectStack.length - 1]
  60. if (parentRunner) {
  61. for (let i = 0; i < childRunner.deps.length; i++) {
  62. const dep = childRunner.deps[i]
  63. if (!dep.has(parentRunner)) {
  64. dep.add(parentRunner)
  65. parentRunner.deps.push(dep)
  66. }
  67. }
  68. }
  69. }