baseHandlers.ts 7.3 KB

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