baseHandlers.ts 6.6 KB

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