| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322 |
- import {
- type Target,
- isReadonly,
- isShallow,
- toRaw,
- toReactive,
- toReadonly,
- } from './reactive'
- import { ITERATE_KEY, MAP_KEY_ITERATE_KEY, track, trigger } from './dep'
- import { ReactiveFlags, TrackOpTypes, TriggerOpTypes } from './constants'
- import {
- capitalize,
- extend,
- hasChanged,
- hasOwn,
- isMap,
- toRawType,
- } from '@vue/shared'
- import { warn } from './warning'
- type CollectionTypes = IterableCollections | WeakCollections
- type IterableCollections = (Map<any, any> | Set<any>) & Target
- type WeakCollections = (WeakMap<any, any> | WeakSet<any>) & Target
- type MapTypes = (Map<any, any> | WeakMap<any, any>) & Target
- type SetTypes = (Set<any> | WeakSet<any>) & Target
- const toShallow = <T extends unknown>(value: T): T => value
- const getProto = <T extends CollectionTypes>(v: T): any =>
- Reflect.getPrototypeOf(v)
- function createIterableMethod(
- method: string | symbol,
- isReadonly: boolean,
- isShallow: boolean,
- ) {
- return function (
- this: IterableCollections,
- ...args: unknown[]
- ): Iterable<unknown> & Iterator<unknown> {
- const target = this[ReactiveFlags.RAW]
- const rawTarget = toRaw(target)
- const targetIsMap = isMap(rawTarget)
- const isPair =
- method === 'entries' || (method === Symbol.iterator && targetIsMap)
- const isKeyOnly = method === 'keys' && targetIsMap
- const innerIterator = target[method](...args)
- const wrap = isShallow ? toShallow : isReadonly ? toReadonly : toReactive
- !isReadonly &&
- track(
- rawTarget,
- TrackOpTypes.ITERATE,
- isKeyOnly ? MAP_KEY_ITERATE_KEY : ITERATE_KEY,
- )
- // return a wrapped iterator which returns observed versions of the
- // values emitted from the real iterator
- return extend(
- // inheriting all iterator properties
- Object.create(innerIterator),
- {
- // iterator protocol
- next() {
- const { value, done } = innerIterator.next()
- return done
- ? { value, done }
- : {
- value: isPair ? [wrap(value[0]), wrap(value[1])] : wrap(value),
- done,
- }
- },
- },
- )
- }
- }
- function createReadonlyMethod(type: TriggerOpTypes): Function {
- return function (this: CollectionTypes, ...args: unknown[]) {
- if (__DEV__) {
- const key = args[0] ? `on key "${args[0]}" ` : ``
- warn(
- `${capitalize(type)} operation ${key}failed: target is readonly.`,
- toRaw(this),
- )
- }
- return type === TriggerOpTypes.DELETE
- ? false
- : type === TriggerOpTypes.CLEAR
- ? undefined
- : this
- }
- }
- type Instrumentations = Record<string | symbol, Function | number>
- function createInstrumentations(
- readonly: boolean,
- shallow: boolean,
- ): Instrumentations {
- const instrumentations: Instrumentations = {
- get(this: MapTypes, key: unknown) {
- // #1772: readonly(reactive(Map)) should return readonly + reactive version
- // of the value
- const target = this[ReactiveFlags.RAW]
- const rawTarget = toRaw(target)
- const rawKey = toRaw(key)
- if (!readonly) {
- if (hasChanged(key, rawKey)) {
- track(rawTarget, TrackOpTypes.GET, key)
- }
- track(rawTarget, TrackOpTypes.GET, rawKey)
- }
- const { has } = getProto(rawTarget)
- const wrap = shallow ? toShallow : readonly ? toReadonly : toReactive
- if (has.call(rawTarget, key)) {
- return wrap(target.get(key))
- } else if (has.call(rawTarget, rawKey)) {
- return wrap(target.get(rawKey))
- } else if (target !== rawTarget) {
- // #3602 readonly(reactive(Map))
- // ensure that the nested reactive `Map` can do tracking for itself
- target.get(key)
- }
- },
- get size() {
- const target = (this as unknown as IterableCollections)[ReactiveFlags.RAW]
- !readonly && track(toRaw(target), TrackOpTypes.ITERATE, ITERATE_KEY)
- return target.size
- },
- has(this: CollectionTypes, key: unknown): boolean {
- const target = this[ReactiveFlags.RAW]
- const rawTarget = toRaw(target)
- const rawKey = toRaw(key)
- if (!readonly) {
- if (hasChanged(key, rawKey)) {
- track(rawTarget, TrackOpTypes.HAS, key)
- }
- track(rawTarget, TrackOpTypes.HAS, rawKey)
- }
- return key === rawKey
- ? target.has(key)
- : target.has(key) || target.has(rawKey)
- },
- forEach(this: IterableCollections, callback: Function, thisArg?: unknown) {
- const observed = this
- const target = observed[ReactiveFlags.RAW]
- const rawTarget = toRaw(target)
- const wrap = shallow ? toShallow : readonly ? toReadonly : toReactive
- !readonly && track(rawTarget, TrackOpTypes.ITERATE, ITERATE_KEY)
- return target.forEach((value: unknown, key: unknown) => {
- // important: make sure the callback is
- // 1. invoked with the reactive map as `this` and 3rd arg
- // 2. the value received should be a corresponding reactive/readonly.
- return callback.call(thisArg, wrap(value), wrap(key), observed)
- })
- },
- }
- extend(
- instrumentations,
- readonly
- ? {
- add: createReadonlyMethod(TriggerOpTypes.ADD),
- set: createReadonlyMethod(TriggerOpTypes.SET),
- delete: createReadonlyMethod(TriggerOpTypes.DELETE),
- clear: createReadonlyMethod(TriggerOpTypes.CLEAR),
- }
- : {
- add(this: SetTypes, value: unknown) {
- if (!shallow && !isShallow(value) && !isReadonly(value)) {
- value = toRaw(value)
- }
- const target = toRaw(this)
- const proto = getProto(target)
- const hadKey = proto.has.call(target, value)
- if (!hadKey) {
- target.add(value)
- trigger(target, TriggerOpTypes.ADD, value, value)
- }
- return this
- },
- set(this: MapTypes, key: unknown, value: unknown) {
- if (!shallow && !isShallow(value) && !isReadonly(value)) {
- value = toRaw(value)
- }
- const target = toRaw(this)
- const { has, get } = getProto(target)
- let hadKey = has.call(target, key)
- if (!hadKey) {
- key = toRaw(key)
- hadKey = has.call(target, key)
- } else if (__DEV__) {
- checkIdentityKeys(target, has, key)
- }
- const oldValue = get.call(target, key)
- target.set(key, value)
- if (!hadKey) {
- trigger(target, TriggerOpTypes.ADD, key, value)
- } else if (hasChanged(value, oldValue)) {
- trigger(target, TriggerOpTypes.SET, key, value, oldValue)
- }
- return this
- },
- delete(this: CollectionTypes, key: unknown) {
- const target = toRaw(this)
- const { has, get } = getProto(target)
- let hadKey = has.call(target, key)
- if (!hadKey) {
- key = toRaw(key)
- hadKey = has.call(target, key)
- } else if (__DEV__) {
- checkIdentityKeys(target, has, key)
- }
- const oldValue = get ? get.call(target, key) : undefined
- // forward the operation before queueing reactions
- const result = target.delete(key)
- if (hadKey) {
- trigger(target, TriggerOpTypes.DELETE, key, undefined, oldValue)
- }
- return result
- },
- clear(this: IterableCollections) {
- const target = toRaw(this)
- const hadItems = target.size !== 0
- const oldTarget = __DEV__
- ? isMap(target)
- ? new Map(target)
- : new Set(target)
- : undefined
- // forward the operation before queueing reactions
- const result = target.clear()
- if (hadItems) {
- trigger(
- target,
- TriggerOpTypes.CLEAR,
- undefined,
- undefined,
- oldTarget,
- )
- }
- return result
- },
- },
- )
- const iteratorMethods = [
- 'keys',
- 'values',
- 'entries',
- Symbol.iterator,
- ] as const
- iteratorMethods.forEach(method => {
- instrumentations[method] = createIterableMethod(method, readonly, shallow)
- })
- return instrumentations
- }
- function createInstrumentationGetter(isReadonly: boolean, shallow: boolean) {
- const instrumentations = createInstrumentations(isReadonly, shallow)
- return (
- target: CollectionTypes,
- key: string | symbol,
- receiver: CollectionTypes,
- ) => {
- if (key === ReactiveFlags.IS_REACTIVE) {
- return !isReadonly
- } else if (key === ReactiveFlags.IS_READONLY) {
- return isReadonly
- } else if (key === ReactiveFlags.RAW) {
- return target
- }
- return Reflect.get(
- hasOwn(instrumentations, key) && key in target
- ? instrumentations
- : target,
- key,
- receiver,
- )
- }
- }
- export const mutableCollectionHandlers: ProxyHandler<CollectionTypes> = {
- get: /*@__PURE__*/ createInstrumentationGetter(false, false),
- }
- export const shallowCollectionHandlers: ProxyHandler<CollectionTypes> = {
- get: /*@__PURE__*/ createInstrumentationGetter(false, true),
- }
- export const readonlyCollectionHandlers: ProxyHandler<CollectionTypes> = {
- get: /*@__PURE__*/ createInstrumentationGetter(true, false),
- }
- export const shallowReadonlyCollectionHandlers: ProxyHandler<CollectionTypes> =
- {
- get: /*@__PURE__*/ createInstrumentationGetter(true, true),
- }
- function checkIdentityKeys(
- target: CollectionTypes,
- has: (key: unknown) => boolean,
- key: unknown,
- ) {
- const rawKey = toRaw(key)
- if (rawKey !== key && has.call(target, rawKey)) {
- const type = toRawType(target)
- warn(
- `Reactive ${type} contains both the raw and reactive ` +
- `versions of the same object${type === `Map` ? ` as keys` : ``}, ` +
- `which can lead to inconsistencies. ` +
- `Avoid differentiating between the raw and reactive versions ` +
- `of an object and only use the reactive version if possible.`,
- )
- }
- }
|