collectionHandlers.ts 6.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236
  1. import { toRaw, reactive, readonly } from './reactive'
  2. import { track, trigger } from './effect'
  3. import { OperationTypes } from './operations'
  4. import { LOCKED } from './lock'
  5. import { isObject, capitalize, hasOwn } from '@vue/shared'
  6. const toReactive = (value: any) => (isObject(value) ? reactive(value) : value)
  7. const toReadonly = (value: any) => (isObject(value) ? readonly(value) : value)
  8. function get(target: any, key: any, wrap: (t: any) => any): any {
  9. target = toRaw(target)
  10. key = toRaw(key)
  11. const proto: any = Reflect.getPrototypeOf(target)
  12. track(target, OperationTypes.GET, key)
  13. const res = proto.get.call(target, key)
  14. return wrap(res)
  15. }
  16. function has(this: any, key: any): boolean {
  17. const target = toRaw(this)
  18. key = toRaw(key)
  19. const proto: any = Reflect.getPrototypeOf(target)
  20. track(target, OperationTypes.HAS, key)
  21. return proto.has.call(target, key)
  22. }
  23. function size(target: any) {
  24. target = toRaw(target)
  25. const proto = Reflect.getPrototypeOf(target)
  26. track(target, OperationTypes.ITERATE)
  27. return Reflect.get(proto, 'size', target)
  28. }
  29. function add(this: any, value: any) {
  30. value = toRaw(value)
  31. const target = toRaw(this)
  32. const proto: any = Reflect.getPrototypeOf(this)
  33. const hadKey = proto.has.call(target, value)
  34. const result = proto.add.call(target, value)
  35. if (!hadKey) {
  36. /* istanbul ignore else */
  37. if (__DEV__) {
  38. trigger(target, OperationTypes.ADD, value, { value })
  39. } else {
  40. trigger(target, OperationTypes.ADD, value)
  41. }
  42. }
  43. return result
  44. }
  45. function set(this: any, key: any, value: any) {
  46. value = toRaw(value)
  47. const target = toRaw(this)
  48. const proto: any = Reflect.getPrototypeOf(this)
  49. const hadKey = proto.has.call(target, key)
  50. const oldValue = proto.get.call(target, key)
  51. const result = proto.set.call(target, key, value)
  52. if (value !== oldValue) {
  53. /* istanbul ignore else */
  54. if (__DEV__) {
  55. const extraInfo = { oldValue, newValue: value }
  56. if (!hadKey) {
  57. trigger(target, OperationTypes.ADD, key, extraInfo)
  58. } else {
  59. trigger(target, OperationTypes.SET, key, extraInfo)
  60. }
  61. } else {
  62. if (!hadKey) {
  63. trigger(target, OperationTypes.ADD, key)
  64. } else {
  65. trigger(target, OperationTypes.SET, key)
  66. }
  67. }
  68. }
  69. return result
  70. }
  71. function deleteEntry(this: any, key: any) {
  72. const target = toRaw(this)
  73. const proto: any = Reflect.getPrototypeOf(this)
  74. const hadKey = proto.has.call(target, key)
  75. const oldValue = proto.get ? proto.get.call(target, key) : undefined
  76. // forward the operation before queueing reactions
  77. const result = proto.delete.call(target, key)
  78. if (hadKey) {
  79. /* istanbul ignore else */
  80. if (__DEV__) {
  81. trigger(target, OperationTypes.DELETE, key, { oldValue })
  82. } else {
  83. trigger(target, OperationTypes.DELETE, key)
  84. }
  85. }
  86. return result
  87. }
  88. function clear(this: any) {
  89. const target = toRaw(this)
  90. const proto: any = Reflect.getPrototypeOf(this)
  91. const hadItems = target.size !== 0
  92. const oldTarget = target instanceof Map ? new Map(target) : new Set(target)
  93. // forward the operation before queueing reactions
  94. const result = proto.clear.call(target)
  95. if (hadItems) {
  96. /* istanbul ignore else */
  97. if (__DEV__) {
  98. trigger(target, OperationTypes.CLEAR, void 0, { oldTarget })
  99. } else {
  100. trigger(target, OperationTypes.CLEAR)
  101. }
  102. }
  103. return result
  104. }
  105. function createForEach(isReadonly: boolean) {
  106. return function forEach(this: any, callback: Function, thisArg?: any) {
  107. const observed = this
  108. const target = toRaw(observed)
  109. const proto: any = Reflect.getPrototypeOf(target)
  110. const wrap = isReadonly ? toReadonly : toReactive
  111. track(target, OperationTypes.ITERATE)
  112. // important: create sure the callback is
  113. // 1. invoked with the reactive map as `this` and 3rd arg
  114. // 2. the value received should be a corresponding reactive/readonly.
  115. function wrappedCallback(value: any, key: any) {
  116. return callback.call(observed, wrap(value), wrap(key), observed)
  117. }
  118. return proto.forEach.call(target, wrappedCallback, thisArg)
  119. }
  120. }
  121. function createIterableMethod(method: string | symbol, isReadonly: boolean) {
  122. return function(this: any, ...args: any[]) {
  123. const target = toRaw(this)
  124. const proto: any = Reflect.getPrototypeOf(target)
  125. const isPair =
  126. method === 'entries' ||
  127. (method === Symbol.iterator && target instanceof Map)
  128. const innerIterator = proto[method].apply(target, args)
  129. const wrap = isReadonly ? toReadonly : toReactive
  130. track(target, OperationTypes.ITERATE)
  131. // return a wrapped iterator which returns observed versions of the
  132. // values emitted from the real iterator
  133. return {
  134. // iterator protocol
  135. next() {
  136. const { value, done } = innerIterator.next()
  137. return done
  138. ? { value, done }
  139. : {
  140. value: isPair ? [wrap(value[0]), wrap(value[1])] : wrap(value),
  141. done
  142. }
  143. },
  144. // iterable protocol
  145. [Symbol.iterator]() {
  146. return this
  147. }
  148. }
  149. }
  150. }
  151. function createReadonlyMethod(
  152. method: Function,
  153. type: OperationTypes
  154. ): Function {
  155. return function(this: any, ...args: any[]) {
  156. if (LOCKED) {
  157. if (__DEV__) {
  158. const key = args[0] ? `on key "${args[0]}" ` : ``
  159. console.warn(
  160. `${capitalize(type)} operation ${key}failed: target is readonly.`,
  161. toRaw(this)
  162. )
  163. }
  164. return type === OperationTypes.DELETE ? false : this
  165. } else {
  166. return method.apply(this, args)
  167. }
  168. }
  169. }
  170. const mutableInstrumentations: any = {
  171. get(key: any) {
  172. return get(this, key, toReactive)
  173. },
  174. get size() {
  175. return size(this)
  176. },
  177. has,
  178. add,
  179. set,
  180. delete: deleteEntry,
  181. clear,
  182. forEach: createForEach(false)
  183. }
  184. const readonlyInstrumentations: any = {
  185. get(key: any) {
  186. return get(this, key, toReadonly)
  187. },
  188. get size() {
  189. return size(this)
  190. },
  191. has,
  192. add: createReadonlyMethod(add, OperationTypes.ADD),
  193. set: createReadonlyMethod(set, OperationTypes.SET),
  194. delete: createReadonlyMethod(deleteEntry, OperationTypes.DELETE),
  195. clear: createReadonlyMethod(clear, OperationTypes.CLEAR),
  196. forEach: createForEach(true)
  197. }
  198. const iteratorMethods = ['keys', 'values', 'entries', Symbol.iterator]
  199. iteratorMethods.forEach(method => {
  200. mutableInstrumentations[method] = createIterableMethod(method, false)
  201. readonlyInstrumentations[method] = createIterableMethod(method, true)
  202. })
  203. function createInstrumentationGetter(instrumentations: any) {
  204. return function getInstrumented(
  205. target: any,
  206. key: string | symbol,
  207. receiver: any
  208. ) {
  209. target =
  210. hasOwn(instrumentations, key) && key in target ? instrumentations : target
  211. return Reflect.get(target, key, receiver)
  212. }
  213. }
  214. export const mutableCollectionHandlers: ProxyHandler<any> = {
  215. get: createInstrumentationGetter(mutableInstrumentations)
  216. }
  217. export const readonlyCollectionHandlers: ProxyHandler<any> = {
  218. get: createInstrumentationGetter(readonlyInstrumentations)
  219. }