reactive.ts 6.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235
  1. import { isObject, toRawType, def } from '@vue/shared'
  2. import {
  3. mutableHandlers,
  4. readonlyHandlers,
  5. shallowReactiveHandlers,
  6. shallowReadonlyHandlers
  7. } from './baseHandlers'
  8. import {
  9. mutableCollectionHandlers,
  10. readonlyCollectionHandlers,
  11. shallowCollectionHandlers,
  12. shallowReadonlyCollectionHandlers
  13. } from './collectionHandlers'
  14. import { UnwrapRefSimple, Ref } from './ref'
  15. export const enum ReactiveFlags {
  16. SKIP = '__v_skip',
  17. IS_REACTIVE = '__v_isReactive',
  18. IS_READONLY = '__v_isReadonly',
  19. RAW = '__v_raw'
  20. }
  21. export interface Target {
  22. [ReactiveFlags.SKIP]?: boolean
  23. [ReactiveFlags.IS_REACTIVE]?: boolean
  24. [ReactiveFlags.IS_READONLY]?: boolean
  25. [ReactiveFlags.RAW]?: any
  26. }
  27. export const reactiveMap = new WeakMap<Target, any>()
  28. export const shallowReactiveMap = new WeakMap<Target, any>()
  29. export const readonlyMap = new WeakMap<Target, any>()
  30. export const shallowReadonlyMap = new WeakMap<Target, any>()
  31. const enum TargetType {
  32. INVALID = 0,
  33. COMMON = 1,
  34. COLLECTION = 2
  35. }
  36. function targetTypeMap(rawType: string) {
  37. switch (rawType) {
  38. case 'Object':
  39. case 'Array':
  40. return TargetType.COMMON
  41. case 'Map':
  42. case 'Set':
  43. case 'WeakMap':
  44. case 'WeakSet':
  45. return TargetType.COLLECTION
  46. default:
  47. return TargetType.INVALID
  48. }
  49. }
  50. function getTargetType(value: Target) {
  51. return value[ReactiveFlags.SKIP] || !Object.isExtensible(value)
  52. ? TargetType.INVALID
  53. : targetTypeMap(toRawType(value))
  54. }
  55. // only unwrap nested ref
  56. export type UnwrapNestedRefs<T> = T extends Ref ? T : UnwrapRefSimple<T>
  57. /**
  58. * Creates a reactive copy of the original object.
  59. *
  60. * The reactive conversion is "deep"—it affects all nested properties. In the
  61. * ES2015 Proxy based implementation, the returned proxy is **not** equal to the
  62. * original object. It is recommended to work exclusively with the reactive
  63. * proxy and avoid relying on the original object.
  64. *
  65. * A reactive object also automatically unwraps refs contained in it, so you
  66. * don't need to use `.value` when accessing and mutating their value:
  67. *
  68. * ```js
  69. * const count = ref(0)
  70. * const obj = reactive({
  71. * count
  72. * })
  73. *
  74. * obj.count++
  75. * obj.count // -> 1
  76. * count.value // -> 1
  77. * ```
  78. */
  79. export function reactive<T extends object>(target: T): UnwrapNestedRefs<T>
  80. export function reactive(target: object) {
  81. // if trying to observe a readonly proxy, return the readonly version.
  82. if (target && (target as Target)[ReactiveFlags.IS_READONLY]) {
  83. return target
  84. }
  85. return createReactiveObject(
  86. target,
  87. false,
  88. mutableHandlers,
  89. mutableCollectionHandlers,
  90. reactiveMap
  91. )
  92. }
  93. /**
  94. * Return a shallowly-reactive copy of the original object, where only the root
  95. * level properties are reactive. It also does not auto-unwrap refs (even at the
  96. * root level).
  97. */
  98. export function shallowReactive<T extends object>(target: T): T {
  99. return createReactiveObject(
  100. target,
  101. false,
  102. shallowReactiveHandlers,
  103. shallowCollectionHandlers,
  104. shallowReactiveMap
  105. )
  106. }
  107. type Primitive = string | number | boolean | bigint | symbol | undefined | null
  108. type Builtin = Primitive | Function | Date | Error | RegExp
  109. export type DeepReadonly<T> = T extends Builtin
  110. ? T
  111. : T extends Map<infer K, infer V>
  112. ? ReadonlyMap<DeepReadonly<K>, DeepReadonly<V>>
  113. : T extends ReadonlyMap<infer K, infer V>
  114. ? ReadonlyMap<DeepReadonly<K>, DeepReadonly<V>>
  115. : T extends WeakMap<infer K, infer V>
  116. ? WeakMap<DeepReadonly<K>, DeepReadonly<V>>
  117. : T extends Set<infer U>
  118. ? ReadonlySet<DeepReadonly<U>>
  119. : T extends ReadonlySet<infer U>
  120. ? ReadonlySet<DeepReadonly<U>>
  121. : T extends WeakSet<infer U>
  122. ? WeakSet<DeepReadonly<U>>
  123. : T extends Promise<infer U>
  124. ? Promise<DeepReadonly<U>>
  125. : T extends {}
  126. ? { readonly [K in keyof T]: DeepReadonly<T[K]> }
  127. : Readonly<T>
  128. /**
  129. * Creates a readonly copy of the original object. Note the returned copy is not
  130. * made reactive, but `readonly` can be called on an already reactive object.
  131. */
  132. export function readonly<T extends object>(
  133. target: T
  134. ): DeepReadonly<UnwrapNestedRefs<T>> {
  135. return createReactiveObject(
  136. target,
  137. true,
  138. readonlyHandlers,
  139. readonlyCollectionHandlers,
  140. readonlyMap
  141. )
  142. }
  143. /**
  144. * Returns a reactive-copy of the original object, where only the root level
  145. * properties are readonly, and does NOT unwrap refs nor recursively convert
  146. * returned properties.
  147. * This is used for creating the props proxy object for stateful components.
  148. */
  149. export function shallowReadonly<T extends object>(
  150. target: T
  151. ): Readonly<{ [K in keyof T]: UnwrapNestedRefs<T[K]> }> {
  152. return createReactiveObject(
  153. target,
  154. true,
  155. shallowReadonlyHandlers,
  156. shallowReadonlyCollectionHandlers,
  157. shallowReadonlyMap
  158. )
  159. }
  160. function createReactiveObject(
  161. target: Target,
  162. isReadonly: boolean,
  163. baseHandlers: ProxyHandler<any>,
  164. collectionHandlers: ProxyHandler<any>,
  165. proxyMap: WeakMap<Target, any>
  166. ) {
  167. if (!isObject(target)) {
  168. if (__DEV__) {
  169. console.warn(`value cannot be made reactive: ${String(target)}`)
  170. }
  171. return target
  172. }
  173. // target is already a Proxy, return it.
  174. // exception: calling readonly() on a reactive object
  175. if (
  176. target[ReactiveFlags.RAW] &&
  177. !(isReadonly && target[ReactiveFlags.IS_REACTIVE])
  178. ) {
  179. return target
  180. }
  181. // target already has corresponding Proxy
  182. const existingProxy = proxyMap.get(target)
  183. if (existingProxy) {
  184. return existingProxy
  185. }
  186. // only a whitelist of value types can be observed.
  187. const targetType = getTargetType(target)
  188. if (targetType === TargetType.INVALID) {
  189. return target
  190. }
  191. const proxy = new Proxy(
  192. target,
  193. targetType === TargetType.COLLECTION ? collectionHandlers : baseHandlers
  194. )
  195. proxyMap.set(target, proxy)
  196. return proxy
  197. }
  198. export function isReactive(value: unknown): boolean {
  199. if (isReadonly(value)) {
  200. return isReactive((value as Target)[ReactiveFlags.RAW])
  201. }
  202. return !!(value && (value as Target)[ReactiveFlags.IS_REACTIVE])
  203. }
  204. export function isReadonly(value: unknown): boolean {
  205. return !!(value && (value as Target)[ReactiveFlags.IS_READONLY])
  206. }
  207. export function isProxy(value: unknown): boolean {
  208. return isReactive(value) || isReadonly(value)
  209. }
  210. export function toRaw<T>(observed: T): T {
  211. const raw = observed && (observed as Target)[ReactiveFlags.RAW]
  212. return raw ? toRaw(raw) : observed
  213. }
  214. export function markRaw<T extends object>(value: T): T {
  215. def(value, ReactiveFlags.SKIP, true)
  216. return value
  217. }