dep.ts 5.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200
  1. import { isArray, isIntegerKey, isMap, isSymbol } from '@vue/shared'
  2. import { type TrackOpTypes, TriggerOpTypes } from './constants'
  3. import { onTrack, triggerEventInfos } from './debug'
  4. import {
  5. type Link,
  6. ReactiveFlags,
  7. type ReactiveNode,
  8. activeSub,
  9. endBatch,
  10. link,
  11. propagate,
  12. shallowPropagate,
  13. startBatch,
  14. } from './system'
  15. class Dep implements ReactiveNode {
  16. _subs: Link | undefined = undefined
  17. subsTail: Link | undefined = undefined
  18. flags: ReactiveFlags = ReactiveFlags.None
  19. constructor(
  20. private map: KeyToDepMap,
  21. private key: unknown,
  22. ) {}
  23. get subs(): Link | undefined {
  24. return this._subs
  25. }
  26. set subs(value: Link | undefined) {
  27. this._subs = value
  28. if (value === undefined) {
  29. this.map.delete(this.key)
  30. }
  31. }
  32. }
  33. // The main WeakMap that stores {target -> key -> dep} connections.
  34. // Conceptually, it's easier to think of a dependency as a Dep class
  35. // which maintains a Set of subscribers, but we simply store them as
  36. // raw Maps to reduce memory overhead.
  37. type KeyToDepMap = Map<any, Dep>
  38. export const targetMap: WeakMap<object, KeyToDepMap> = new WeakMap()
  39. export const ITERATE_KEY: unique symbol = Symbol(
  40. __DEV__ ? 'Object iterate' : '',
  41. )
  42. export const MAP_KEY_ITERATE_KEY: unique symbol = Symbol(
  43. __DEV__ ? 'Map keys iterate' : '',
  44. )
  45. export const ARRAY_ITERATE_KEY: unique symbol = Symbol(
  46. __DEV__ ? 'Array iterate' : '',
  47. )
  48. /**
  49. * Tracks access to a reactive property.
  50. *
  51. * This will check which effect is running at the moment and record it as dep
  52. * which records all effects that depend on the reactive property.
  53. *
  54. * @param target - Object holding the reactive property.
  55. * @param type - Defines the type of access to the reactive property.
  56. * @param key - Identifier of the reactive property to track.
  57. */
  58. export function track(target: object, type: TrackOpTypes, key: unknown): void {
  59. if (activeSub !== undefined) {
  60. let depsMap = targetMap.get(target)
  61. if (!depsMap) {
  62. targetMap.set(target, (depsMap = new Map()))
  63. }
  64. let dep = depsMap.get(key)
  65. if (!dep) {
  66. depsMap.set(key, (dep = new Dep(depsMap, key)))
  67. }
  68. if (__DEV__) {
  69. onTrack(activeSub!, {
  70. target,
  71. type,
  72. key,
  73. })
  74. }
  75. link(dep, activeSub!)
  76. }
  77. }
  78. /**
  79. * Finds all deps associated with the target (or a specific property) and
  80. * triggers the effects stored within.
  81. *
  82. * @param target - The reactive object.
  83. * @param type - Defines the type of the operation that needs to trigger effects.
  84. * @param key - Can be used to target a specific reactive property in the target object.
  85. */
  86. export function trigger(
  87. target: object,
  88. type: TriggerOpTypes,
  89. key?: unknown,
  90. newValue?: unknown,
  91. oldValue?: unknown,
  92. oldTarget?: Map<unknown, unknown> | Set<unknown>,
  93. ): void {
  94. const depsMap = targetMap.get(target)
  95. if (!depsMap) {
  96. // never been tracked
  97. return
  98. }
  99. const run = (dep: ReactiveNode | undefined) => {
  100. if (dep !== undefined && dep.subs !== undefined) {
  101. if (__DEV__) {
  102. triggerEventInfos.push({
  103. target,
  104. type,
  105. key,
  106. newValue,
  107. oldValue,
  108. oldTarget,
  109. })
  110. }
  111. propagate(dep.subs)
  112. shallowPropagate(dep.subs)
  113. if (__DEV__) {
  114. triggerEventInfos.pop()
  115. }
  116. }
  117. }
  118. startBatch()
  119. if (type === TriggerOpTypes.CLEAR) {
  120. // collection being cleared
  121. // trigger all effects for target
  122. depsMap.forEach(run)
  123. } else {
  124. const targetIsArray = isArray(target)
  125. const isArrayIndex = targetIsArray && isIntegerKey(key)
  126. if (targetIsArray && key === 'length') {
  127. const newLength = Number(newValue)
  128. depsMap.forEach((dep, key) => {
  129. if (
  130. key === 'length' ||
  131. key === ARRAY_ITERATE_KEY ||
  132. (!isSymbol(key) && key >= newLength)
  133. ) {
  134. run(dep)
  135. }
  136. })
  137. } else {
  138. // schedule runs for SET | ADD | DELETE
  139. if (key !== void 0 || depsMap.has(void 0)) {
  140. run(depsMap.get(key))
  141. }
  142. // schedule ARRAY_ITERATE for any numeric key change (length is handled above)
  143. if (isArrayIndex) {
  144. run(depsMap.get(ARRAY_ITERATE_KEY))
  145. }
  146. // also run for iteration key on ADD | DELETE | Map.SET
  147. switch (type) {
  148. case TriggerOpTypes.ADD:
  149. if (!targetIsArray) {
  150. run(depsMap.get(ITERATE_KEY))
  151. if (isMap(target)) {
  152. run(depsMap.get(MAP_KEY_ITERATE_KEY))
  153. }
  154. } else if (isArrayIndex) {
  155. // new index added to array -> length changes
  156. run(depsMap.get('length'))
  157. }
  158. break
  159. case TriggerOpTypes.DELETE:
  160. if (!targetIsArray) {
  161. run(depsMap.get(ITERATE_KEY))
  162. if (isMap(target)) {
  163. run(depsMap.get(MAP_KEY_ITERATE_KEY))
  164. }
  165. }
  166. break
  167. case TriggerOpTypes.SET:
  168. if (isMap(target)) {
  169. run(depsMap.get(ITERATE_KEY))
  170. }
  171. break
  172. }
  173. }
  174. }
  175. endBatch()
  176. }
  177. export function getDepFromReactive(
  178. object: any,
  179. key: string | number | symbol,
  180. ): ReactiveNode | undefined {
  181. const depMap = targetMap.get(object)
  182. return depMap && depMap.get(key)
  183. }