baseHandlers.ts 6.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250
  1. import {
  2. reactive,
  3. readonly,
  4. toRaw,
  5. ReactiveFlags,
  6. Target,
  7. readonlyMap,
  8. reactiveMap,
  9. shallowReactiveMap,
  10. shallowReadonlyMap
  11. } from './reactive'
  12. import { TrackOpTypes, TriggerOpTypes } from './operations'
  13. import {
  14. track,
  15. trigger,
  16. ITERATE_KEY,
  17. pauseTracking,
  18. resetTracking
  19. } from './effect'
  20. import {
  21. isObject,
  22. hasOwn,
  23. isSymbol,
  24. hasChanged,
  25. isArray,
  26. isIntegerKey,
  27. extend,
  28. makeMap
  29. } from '@vue/shared'
  30. import { isRef } from './ref'
  31. const isNonTrackableKeys = /*#__PURE__*/ makeMap(`__proto__,__v_isRef,__isVue`)
  32. const builtInSymbols = new Set(
  33. Object.getOwnPropertyNames(Symbol)
  34. .map(key => (Symbol as any)[key])
  35. .filter(isSymbol)
  36. )
  37. const get = /*#__PURE__*/ createGetter()
  38. const shallowGet = /*#__PURE__*/ createGetter(false, true)
  39. const readonlyGet = /*#__PURE__*/ createGetter(true)
  40. const shallowReadonlyGet = /*#__PURE__*/ createGetter(true, true)
  41. const arrayInstrumentations = /*#__PURE__*/ createArrayInstrumentations()
  42. function createArrayInstrumentations() {
  43. const instrumentations: Record<string, Function> = {}
  44. // instrument identity-sensitive Array methods to account for possible reactive
  45. // values
  46. ;(['includes', 'indexOf', 'lastIndexOf'] as const).forEach(key => {
  47. const method = Array.prototype[key] as any
  48. instrumentations[key] = function(this: unknown[], ...args: unknown[]) {
  49. const arr = toRaw(this)
  50. for (let i = 0, l = this.length; i < l; i++) {
  51. track(arr, TrackOpTypes.GET, i + '')
  52. }
  53. // we run the method using the original args first (which may be reactive)
  54. const res = method.apply(arr, args)
  55. if (res === -1 || res === false) {
  56. // if that didn't work, run it again using raw values.
  57. return method.apply(arr, args.map(toRaw))
  58. } else {
  59. return res
  60. }
  61. }
  62. })
  63. // instrument length-altering mutation methods to avoid length being tracked
  64. // which leads to infinite loops in some cases (#2137)
  65. ;(['push', 'pop', 'shift', 'unshift', 'splice'] as const).forEach(key => {
  66. const method = Array.prototype[key] as any
  67. instrumentations[key] = function(this: unknown[], ...args: unknown[]) {
  68. pauseTracking()
  69. const res = method.apply(this, args)
  70. resetTracking()
  71. return res
  72. }
  73. })
  74. return instrumentations
  75. }
  76. function createGetter(isReadonly = false, shallow = false) {
  77. return function get(target: Target, key: string | symbol, receiver: object) {
  78. if (key === ReactiveFlags.IS_REACTIVE) {
  79. return !isReadonly
  80. } else if (key === ReactiveFlags.IS_READONLY) {
  81. return isReadonly
  82. } else if (
  83. key === ReactiveFlags.RAW &&
  84. receiver ===
  85. (isReadonly
  86. ? shallow
  87. ? shallowReadonlyMap
  88. : readonlyMap
  89. : shallow
  90. ? shallowReactiveMap
  91. : reactiveMap
  92. ).get(target)
  93. ) {
  94. return target
  95. }
  96. const targetIsArray = isArray(target)
  97. if (!isReadonly && targetIsArray && hasOwn(arrayInstrumentations, key)) {
  98. return Reflect.get(arrayInstrumentations, key, receiver)
  99. }
  100. const res = Reflect.get(target, key, receiver)
  101. if (isSymbol(key) ? builtInSymbols.has(key) : isNonTrackableKeys(key)) {
  102. return res
  103. }
  104. if (!isReadonly) {
  105. track(target, TrackOpTypes.GET, key)
  106. }
  107. if (shallow) {
  108. return res
  109. }
  110. if (isRef(res)) {
  111. // ref unwrapping - does not apply for Array + integer key.
  112. const shouldUnwrap = !targetIsArray || !isIntegerKey(key)
  113. return shouldUnwrap ? res.value : res
  114. }
  115. if (isObject(res)) {
  116. // Convert returned value into a proxy as well. we do the isObject check
  117. // here to avoid invalid value warning. Also need to lazy access readonly
  118. // and reactive here to avoid circular dependency.
  119. return isReadonly ? readonly(res) : reactive(res)
  120. }
  121. return res
  122. }
  123. }
  124. const set = /*#__PURE__*/ createSetter()
  125. const shallowSet = /*#__PURE__*/ createSetter(true)
  126. function createSetter(shallow = false) {
  127. return function set(
  128. target: object,
  129. key: string | symbol,
  130. value: unknown,
  131. receiver: object
  132. ): boolean {
  133. let oldValue = (target as any)[key]
  134. if (!shallow) {
  135. value = toRaw(value)
  136. oldValue = toRaw(oldValue)
  137. if (!isArray(target) && isRef(oldValue) && !isRef(value)) {
  138. oldValue.value = value
  139. return true
  140. }
  141. } else {
  142. // in shallow mode, objects are set as-is regardless of reactive or not
  143. }
  144. const hadKey =
  145. isArray(target) && isIntegerKey(key)
  146. ? Number(key) < target.length
  147. : hasOwn(target, key)
  148. const result = Reflect.set(target, key, value, receiver)
  149. // don't trigger if target is something up in the prototype chain of original
  150. if (target === toRaw(receiver)) {
  151. if (!hadKey) {
  152. trigger(target, TriggerOpTypes.ADD, key, value)
  153. } else if (hasChanged(value, oldValue)) {
  154. trigger(target, TriggerOpTypes.SET, key, value, oldValue)
  155. }
  156. }
  157. return result
  158. }
  159. }
  160. function deleteProperty(target: object, key: string | symbol): boolean {
  161. const hadKey = hasOwn(target, key)
  162. const oldValue = (target as any)[key]
  163. const result = Reflect.deleteProperty(target, key)
  164. if (result && hadKey) {
  165. trigger(target, TriggerOpTypes.DELETE, key, undefined, oldValue)
  166. }
  167. return result
  168. }
  169. function has(target: object, key: string | symbol): boolean {
  170. const result = Reflect.has(target, key)
  171. if (!isSymbol(key) || !builtInSymbols.has(key)) {
  172. track(target, TrackOpTypes.HAS, key)
  173. }
  174. return result
  175. }
  176. function ownKeys(target: object): (string | symbol)[] {
  177. track(target, TrackOpTypes.ITERATE, isArray(target) ? 'length' : ITERATE_KEY)
  178. return Reflect.ownKeys(target)
  179. }
  180. export const mutableHandlers: ProxyHandler<object> = {
  181. get,
  182. set,
  183. deleteProperty,
  184. has,
  185. ownKeys
  186. }
  187. export const readonlyHandlers: ProxyHandler<object> = {
  188. get: readonlyGet,
  189. set(target, key) {
  190. if (__DEV__) {
  191. console.warn(
  192. `Set operation on key "${String(key)}" failed: target is readonly.`,
  193. target
  194. )
  195. }
  196. return true
  197. },
  198. deleteProperty(target, key) {
  199. if (__DEV__) {
  200. console.warn(
  201. `Delete operation on key "${String(key)}" failed: target is readonly.`,
  202. target
  203. )
  204. }
  205. return true
  206. }
  207. }
  208. export const shallowReactiveHandlers = /*#__PURE__*/ extend(
  209. {},
  210. mutableHandlers,
  211. {
  212. get: shallowGet,
  213. set: shallowSet
  214. }
  215. )
  216. // Props handlers are special in the sense that it should not unwrap top-level
  217. // refs (in order to allow refs to be explicitly passed down), but should
  218. // retain the reactivity of the normal readonly object.
  219. export const shallowReadonlyHandlers = /*#__PURE__*/ extend(
  220. {},
  221. readonlyHandlers,
  222. {
  223. get: shallowReadonlyGet
  224. }
  225. )