baseHandlers.ts 5.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175
  1. import { reactive, readonly, toRaw } from './reactive'
  2. import { TrackOpTypes, TriggerOpTypes } from './operations'
  3. import { track, trigger, ITERATE_KEY } from './effect'
  4. import { LOCKED } from './lock'
  5. import { isObject, hasOwn, isSymbol, hasChanged, isArray } from '@vue/shared'
  6. import { isRef } from './ref'
  7. const builtInSymbols = new Set(
  8. Object.getOwnPropertyNames(Symbol)
  9. .map(key => (Symbol as any)[key])
  10. .filter(isSymbol)
  11. )
  12. const get = /*#__PURE__*/ createGetter()
  13. const readonlyGet = /*#__PURE__*/ createGetter(true)
  14. const shallowReadonlyGet = /*#__PURE__*/ createGetter(true, true)
  15. const arrayIdentityInstrumentations: Record<string, Function> = {}
  16. ;['includes', 'indexOf', 'lastIndexOf'].forEach(key => {
  17. arrayIdentityInstrumentations[key] = function(
  18. value: unknown,
  19. ...args: any[]
  20. ): any {
  21. return toRaw(this)[key](toRaw(value), ...args)
  22. }
  23. })
  24. function createGetter(isReadonly = false, shallow = false) {
  25. return function get(target: object, key: string | symbol, receiver: object) {
  26. if (isArray(target) && hasOwn(arrayIdentityInstrumentations, key)) {
  27. return Reflect.get(arrayIdentityInstrumentations, key, receiver)
  28. }
  29. const res = Reflect.get(target, key, receiver)
  30. if (isSymbol(key) && builtInSymbols.has(key)) {
  31. return res
  32. }
  33. if (shallow) {
  34. track(target, TrackOpTypes.GET, key)
  35. // TODO strict mode that returns a shallow-readonly version of the value
  36. return res
  37. }
  38. if (isRef(res)) {
  39. return res.value
  40. }
  41. track(target, TrackOpTypes.GET, key)
  42. return isObject(res)
  43. ? isReadonly
  44. ? // need to lazy access readonly and reactive here to avoid
  45. // circular dependency
  46. readonly(res)
  47. : reactive(res)
  48. : res
  49. }
  50. }
  51. const set = /*#__PURE__*/ createSetter()
  52. const readonlySet = /*#__PURE__*/ createSetter(true)
  53. const shallowReadonlySet = /*#__PURE__*/ createSetter(true, true)
  54. function createSetter(isReadonly = false, shallow = false) {
  55. return function set(
  56. target: object,
  57. key: string | symbol,
  58. value: unknown,
  59. receiver: object
  60. ): boolean {
  61. if (isReadonly && LOCKED) {
  62. if (__DEV__) {
  63. console.warn(
  64. `Set operation on key "${String(key)}" failed: target is readonly.`,
  65. target
  66. )
  67. }
  68. return true
  69. }
  70. const oldValue = (target as any)[key]
  71. if (!shallow) {
  72. value = toRaw(value)
  73. if (isRef(oldValue) && !isRef(value)) {
  74. oldValue.value = value
  75. return true
  76. }
  77. } else {
  78. // in shallow mode, objects are set as-is regardless of reactive or not
  79. }
  80. const hadKey = hasOwn(target, key)
  81. const result = Reflect.set(target, key, value, receiver)
  82. // don't trigger if target is something up in the prototype chain of original
  83. if (target === toRaw(receiver)) {
  84. /* istanbul ignore else */
  85. if (__DEV__) {
  86. const extraInfo = { oldValue, newValue: value }
  87. if (!hadKey) {
  88. trigger(target, TriggerOpTypes.ADD, key, extraInfo)
  89. } else if (hasChanged(value, oldValue)) {
  90. trigger(target, TriggerOpTypes.SET, key, extraInfo)
  91. }
  92. } else {
  93. if (!hadKey) {
  94. trigger(target, TriggerOpTypes.ADD, key)
  95. } else if (hasChanged(value, oldValue)) {
  96. trigger(target, TriggerOpTypes.SET, key)
  97. }
  98. }
  99. }
  100. return result
  101. }
  102. }
  103. function deleteProperty(target: object, key: string | symbol): boolean {
  104. const hadKey = hasOwn(target, key)
  105. const oldValue = (target as any)[key]
  106. const result = Reflect.deleteProperty(target, key)
  107. if (result && hadKey) {
  108. /* istanbul ignore else */
  109. if (__DEV__) {
  110. trigger(target, TriggerOpTypes.DELETE, key, { oldValue })
  111. } else {
  112. trigger(target, TriggerOpTypes.DELETE, key)
  113. }
  114. }
  115. return result
  116. }
  117. function has(target: object, key: string | symbol): boolean {
  118. const result = Reflect.has(target, key)
  119. track(target, TrackOpTypes.HAS, key)
  120. return result
  121. }
  122. function ownKeys(target: object): (string | number | symbol)[] {
  123. track(target, TrackOpTypes.ITERATE, ITERATE_KEY)
  124. return Reflect.ownKeys(target)
  125. }
  126. export const mutableHandlers: ProxyHandler<object> = {
  127. get,
  128. set,
  129. deleteProperty,
  130. has,
  131. ownKeys
  132. }
  133. export const readonlyHandlers: ProxyHandler<object> = {
  134. get: readonlyGet,
  135. set: readonlySet,
  136. has,
  137. ownKeys,
  138. deleteProperty(target: object, key: string | symbol): boolean {
  139. if (LOCKED) {
  140. if (__DEV__) {
  141. console.warn(
  142. `Delete operation on key "${String(
  143. key
  144. )}" failed: target is readonly.`,
  145. target
  146. )
  147. }
  148. return true
  149. } else {
  150. return deleteProperty(target, key)
  151. }
  152. }
  153. }
  154. // Props handlers are special in the sense that it should not unwrap top-level
  155. // refs (in order to allow refs to be explicitly passed down), but should
  156. // retain the reactivity of the normal readonly object.
  157. export const shallowReadonlyHandlers: ProxyHandler<object> = {
  158. ...readonlyHandlers,
  159. get: shallowReadonlyGet,
  160. set: shallowReadonlySet
  161. }