baseHandlers.ts 7.4 KB

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