effect.ts 5.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231
  1. import { OperationTypes } from './operations'
  2. import { Dep, targetMap } from './reactive'
  3. import { EMPTY_OBJ, extend } from '@vue/shared'
  4. export interface ReactiveEffect {
  5. (): any
  6. isEffect: true
  7. active: boolean
  8. raw: Function
  9. deps: Array<Dep>
  10. computed?: boolean
  11. scheduler?: (run: Function) => void
  12. onTrack?: (event: DebuggerEvent) => void
  13. onTrigger?: (event: DebuggerEvent) => void
  14. onStop?: () => void
  15. }
  16. export interface ReactiveEffectOptions {
  17. lazy?: boolean
  18. computed?: boolean
  19. scheduler?: (run: Function) => void
  20. onTrack?: (event: DebuggerEvent) => void
  21. onTrigger?: (event: DebuggerEvent) => void
  22. onStop?: () => void
  23. }
  24. export interface DebuggerEvent {
  25. effect: ReactiveEffect
  26. target: any
  27. type: OperationTypes
  28. key: string | symbol | undefined
  29. }
  30. export const activeReactiveEffectStack: ReactiveEffect[] = []
  31. export const ITERATE_KEY = Symbol('iterate')
  32. export function effect(
  33. fn: Function,
  34. options: ReactiveEffectOptions = EMPTY_OBJ
  35. ): ReactiveEffect {
  36. if ((fn as ReactiveEffect).isEffect) {
  37. fn = (fn as ReactiveEffect).raw
  38. }
  39. const effect = createReactiveEffect(fn, options)
  40. if (!options.lazy) {
  41. effect()
  42. }
  43. return effect
  44. }
  45. export function stop(effect: ReactiveEffect) {
  46. if (effect.active) {
  47. cleanup(effect)
  48. if (effect.onStop) {
  49. effect.onStop()
  50. }
  51. effect.active = false
  52. }
  53. }
  54. function createReactiveEffect(
  55. fn: Function,
  56. options: ReactiveEffectOptions
  57. ): ReactiveEffect {
  58. const effect = function effect(...args): any {
  59. return run(effect as ReactiveEffect, fn, args)
  60. } as ReactiveEffect
  61. effect.isEffect = true
  62. effect.active = true
  63. effect.raw = fn
  64. effect.scheduler = options.scheduler
  65. effect.onTrack = options.onTrack
  66. effect.onTrigger = options.onTrigger
  67. effect.onStop = options.onStop
  68. effect.computed = options.computed
  69. effect.deps = []
  70. return effect
  71. }
  72. function run(effect: ReactiveEffect, fn: Function, args: any[]): any {
  73. if (!effect.active) {
  74. return fn(...args)
  75. }
  76. if (activeReactiveEffectStack.indexOf(effect) === -1) {
  77. cleanup(effect)
  78. try {
  79. activeReactiveEffectStack.push(effect)
  80. return fn(...args)
  81. } finally {
  82. activeReactiveEffectStack.pop()
  83. }
  84. }
  85. }
  86. function cleanup(effect: ReactiveEffect) {
  87. const { deps } = effect
  88. if (deps.length) {
  89. for (let i = 0; i < deps.length; i++) {
  90. deps[i].delete(effect)
  91. }
  92. deps.length = 0
  93. }
  94. }
  95. let shouldTrack = true
  96. export function pauseTracking() {
  97. shouldTrack = false
  98. }
  99. export function resumeTracking() {
  100. shouldTrack = true
  101. }
  102. export function track(
  103. target: any,
  104. type: OperationTypes,
  105. key?: string | symbol
  106. ) {
  107. if (!shouldTrack) {
  108. return
  109. }
  110. const effect = activeReactiveEffectStack[activeReactiveEffectStack.length - 1]
  111. if (effect) {
  112. if (type === OperationTypes.ITERATE) {
  113. key = ITERATE_KEY
  114. }
  115. let depsMap = targetMap.get(target)
  116. if (depsMap === void 0) {
  117. targetMap.set(target, (depsMap = new Map()))
  118. }
  119. let dep = depsMap.get(key!)
  120. if (dep === void 0) {
  121. depsMap.set(key!, (dep = new Set()))
  122. }
  123. if (!dep.has(effect)) {
  124. dep.add(effect)
  125. effect.deps.push(dep)
  126. if (__DEV__ && effect.onTrack) {
  127. effect.onTrack({
  128. effect,
  129. target,
  130. type,
  131. key
  132. })
  133. }
  134. }
  135. }
  136. }
  137. export function trigger(
  138. target: any,
  139. type: OperationTypes,
  140. key?: string | symbol,
  141. extraInfo?: any
  142. ) {
  143. const depsMap = targetMap.get(target)
  144. if (depsMap === void 0) {
  145. // never been tracked
  146. return
  147. }
  148. const effects = new Set<ReactiveEffect>()
  149. const computedRunners = new Set<ReactiveEffect>()
  150. if (type === OperationTypes.CLEAR) {
  151. // collection being cleared, trigger all effects for target
  152. depsMap.forEach(dep => {
  153. addRunners(effects, computedRunners, dep)
  154. })
  155. } else {
  156. // schedule runs for SET | ADD | DELETE
  157. if (key !== void 0) {
  158. addRunners(effects, computedRunners, depsMap.get(key))
  159. }
  160. // also run for iteration key on ADD | DELETE
  161. if (type === OperationTypes.ADD || type === OperationTypes.DELETE) {
  162. const iterationKey = Array.isArray(target) ? 'length' : ITERATE_KEY
  163. addRunners(effects, computedRunners, depsMap.get(iterationKey))
  164. }
  165. }
  166. const run = (effect: ReactiveEffect) => {
  167. scheduleRun(effect, target, type, key, extraInfo)
  168. }
  169. // Important: computed effects must be run first so that computed getters
  170. // can be invalidated before any normal effects that depend on them are run.
  171. computedRunners.forEach(run)
  172. effects.forEach(run)
  173. }
  174. function addRunners(
  175. effects: Set<ReactiveEffect>,
  176. computedRunners: Set<ReactiveEffect>,
  177. effectsToAdd: Set<ReactiveEffect> | undefined
  178. ) {
  179. if (effectsToAdd !== void 0) {
  180. effectsToAdd.forEach(effect => {
  181. if (effect.computed) {
  182. computedRunners.add(effect)
  183. } else {
  184. effects.add(effect)
  185. }
  186. })
  187. }
  188. }
  189. function scheduleRun(
  190. effect: ReactiveEffect,
  191. target: any,
  192. type: OperationTypes,
  193. key: string | symbol | undefined,
  194. extraInfo: any
  195. ) {
  196. if (__DEV__ && effect.onTrigger) {
  197. effect.onTrigger(
  198. extend(
  199. {
  200. effect,
  201. target,
  202. key,
  203. type
  204. },
  205. extraInfo
  206. )
  207. )
  208. }
  209. if (effect.scheduler !== void 0) {
  210. effect.scheduler(effect)
  211. } else {
  212. effect()
  213. }
  214. }