baseHandlers.ts 7.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269
  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 wasRef = isRef(target)
  91. const res = Reflect.get(
  92. target,
  93. key,
  94. // if this is a proxy wrapping a ref, return methods using the raw ref
  95. // as receiver so that we don't have to call `toRaw` on the ref in all
  96. // its class methods
  97. wasRef ? target : receiver,
  98. )
  99. if (wasRef && key !== 'value') {
  100. return res
  101. }
  102. if (isSymbol(key) ? builtInSymbols.has(key) : isNonTrackableKeys(key)) {
  103. return res
  104. }
  105. if (!isReadonly) {
  106. track(target, TrackOpTypes.GET, key)
  107. }
  108. if (isShallow) {
  109. return res
  110. }
  111. if (isRef(res)) {
  112. // ref unwrapping - skip unwrap for Array + integer key.
  113. const value = targetIsArray && isIntegerKey(key) ? res : res.value
  114. return isReadonly && isObject(value) ? readonly(value) : value
  115. }
  116. if (isObject(res)) {
  117. // Convert returned value into a proxy as well. we do the isObject check
  118. // here to avoid invalid value warning. Also need to lazy access readonly
  119. // and reactive here to avoid circular dependency.
  120. return isReadonly ? readonly(res) : reactive(res)
  121. }
  122. return res
  123. }
  124. }
  125. class MutableReactiveHandler extends BaseReactiveHandler {
  126. constructor(isShallow = false) {
  127. super(false, isShallow)
  128. }
  129. set(
  130. target: Record<string | symbol, unknown>,
  131. key: string | symbol,
  132. value: unknown,
  133. receiver: object,
  134. ): boolean {
  135. let oldValue = target[key]
  136. const isArrayWithIntegerKey = isArray(target) && isIntegerKey(key)
  137. if (!this._isShallow) {
  138. const isOldValueReadonly = isReadonly(oldValue)
  139. if (!isShallow(value) && !isReadonly(value)) {
  140. oldValue = toRaw(oldValue)
  141. value = toRaw(value)
  142. }
  143. if (!isArrayWithIntegerKey && isRef(oldValue) && !isRef(value)) {
  144. if (isOldValueReadonly) {
  145. if (__DEV__) {
  146. warn(
  147. `Set operation on key "${String(key)}" failed: target is readonly.`,
  148. target[key],
  149. )
  150. }
  151. return true
  152. } else {
  153. oldValue.value = value
  154. return true
  155. }
  156. }
  157. } else {
  158. // in shallow mode, objects are set as-is regardless of reactive or not
  159. }
  160. const hadKey = isArrayWithIntegerKey
  161. ? Number(key) < target.length
  162. : hasOwn(target, key)
  163. const result = Reflect.set(
  164. target,
  165. key,
  166. value,
  167. isRef(target) ? target : receiver,
  168. )
  169. // don't trigger if target is something up in the prototype chain of original
  170. if (target === toRaw(receiver)) {
  171. if (!hadKey) {
  172. trigger(target, TriggerOpTypes.ADD, key, value)
  173. } else if (hasChanged(value, oldValue)) {
  174. trigger(target, TriggerOpTypes.SET, key, value, oldValue)
  175. }
  176. }
  177. return result
  178. }
  179. deleteProperty(
  180. target: Record<string | symbol, unknown>,
  181. key: string | symbol,
  182. ): boolean {
  183. const hadKey = hasOwn(target, key)
  184. const oldValue = target[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. has(target: Record<string | symbol, unknown>, 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. ownKeys(target: Record<string | symbol, unknown>): (string | symbol)[] {
  199. track(
  200. target,
  201. TrackOpTypes.ITERATE,
  202. isArray(target) ? 'length' : ITERATE_KEY,
  203. )
  204. return Reflect.ownKeys(target)
  205. }
  206. }
  207. class ReadonlyReactiveHandler extends BaseReactiveHandler {
  208. constructor(isShallow = false) {
  209. super(true, isShallow)
  210. }
  211. set(target: object, key: string | symbol) {
  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: object, key: string | symbol) {
  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 mutableHandlers: ProxyHandler<object> =
  231. /*@__PURE__*/ new MutableReactiveHandler()
  232. export const readonlyHandlers: ProxyHandler<object> =
  233. /*@__PURE__*/ new ReadonlyReactiveHandler()
  234. export const shallowReactiveHandlers: MutableReactiveHandler =
  235. /*@__PURE__*/ new MutableReactiveHandler(true)
  236. // Props handlers are special in the sense that it should not unwrap top-level
  237. // refs (in order to allow refs to be explicitly passed down), but should
  238. // retain the reactivity of the normal readonly object.
  239. export const shallowReadonlyHandlers: ReadonlyReactiveHandler =
  240. /*@__PURE__*/ new ReadonlyReactiveHandler(true)