| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393 |
- import { toRaw, reactive, readonly, ReactiveFlags } from './reactive'
- import { track, trigger, ITERATE_KEY, MAP_KEY_ITERATE_KEY } from './effect'
- import { TrackOpTypes, TriggerOpTypes } from './operations'
- import {
- isObject,
- capitalize,
- hasOwn,
- hasChanged,
- toRawType,
- isMap
- } from '@vue/shared'
- export type CollectionTypes = IterableCollections | WeakCollections
- type IterableCollections = Map<any, any> | Set<any>
- type WeakCollections = WeakMap<any, any> | WeakSet<any>
- type MapTypes = Map<any, any> | WeakMap<any, any>
- type SetTypes = Set<any> | WeakSet<any>
- const toReactive = <T extends unknown>(value: T): T =>
- isObject(value) ? reactive(value) : value
- const toReadonly = <T extends unknown>(value: T): T =>
- isObject(value) ? readonly(value as Record<any, any>) : value
- const toShallow = <T extends unknown>(value: T): T => value
- const getProto = <T extends CollectionTypes>(v: T): any =>
- Reflect.getPrototypeOf(v)
- function get(
- target: MapTypes,
- key: unknown,
- isReadonly = false,
- isShallow = false
- ) {
- // #1772: readonly(reactive(Map)) should return readonly + reactive version
- // of the value
- target = (target as any)[ReactiveFlags.RAW]
- const rawTarget = toRaw(target)
- const rawKey = toRaw(key)
- if (key !== rawKey) {
- !isReadonly && track(rawTarget, TrackOpTypes.GET, key)
- }
- !isReadonly && track(rawTarget, TrackOpTypes.GET, rawKey)
- const { has } = getProto(rawTarget)
- const wrap = isShallow ? toShallow : isReadonly ? 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)
- }
- }
- function has(this: CollectionTypes, key: unknown, isReadonly = false): boolean {
- const target = (this as any)[ReactiveFlags.RAW]
- const rawTarget = toRaw(target)
- const rawKey = toRaw(key)
- if (key !== rawKey) {
- !isReadonly && track(rawTarget, TrackOpTypes.HAS, key)
- }
- !isReadonly && track(rawTarget, TrackOpTypes.HAS, rawKey)
- return key === rawKey
- ? target.has(key)
- : target.has(key) || target.has(rawKey)
- }
- function size(target: IterableCollections, isReadonly = false) {
- target = (target as any)[ReactiveFlags.RAW]
- !isReadonly && track(toRaw(target), TrackOpTypes.ITERATE, ITERATE_KEY)
- return Reflect.get(target, 'size', target)
- }
- function add(this: SetTypes, value: unknown) {
- 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
- }
- function set(this: MapTypes, key: unknown, value: unknown) {
- 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
- }
- function deleteEntry(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
- }
- function 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
- }
- function createForEach(isReadonly: boolean, isShallow: boolean) {
- return function forEach(
- this: IterableCollections,
- callback: Function,
- thisArg?: unknown
- ) {
- const observed = this as any
- const target = observed[ReactiveFlags.RAW]
- const rawTarget = toRaw(target)
- const wrap = isShallow ? toShallow : isReadonly ? toReadonly : toReactive
- !isReadonly && 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)
- })
- }
- }
- interface Iterable {
- [Symbol.iterator](): Iterator
- }
- interface Iterator {
- next(value?: any): IterationResult
- }
- interface IterationResult {
- value: any
- done: boolean
- }
- function createIterableMethod(
- method: string | symbol,
- isReadonly: boolean,
- isShallow: boolean
- ) {
- return function(
- this: IterableCollections,
- ...args: unknown[]
- ): Iterable & Iterator {
- const target = (this as any)[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 {
- // iterator protocol
- next() {
- const { value, done } = innerIterator.next()
- return done
- ? { value, done }
- : {
- value: isPair ? [wrap(value[0]), wrap(value[1])] : wrap(value),
- done
- }
- },
- // iterable protocol
- [Symbol.iterator]() {
- return this
- }
- }
- }
- }
- function createReadonlyMethod(type: TriggerOpTypes): Function {
- return function(this: CollectionTypes, ...args: unknown[]) {
- if (__DEV__) {
- const key = args[0] ? `on key "${args[0]}" ` : ``
- console.warn(
- `${capitalize(type)} operation ${key}failed: target is readonly.`,
- toRaw(this)
- )
- }
- return type === TriggerOpTypes.DELETE ? false : this
- }
- }
- const mutableInstrumentations: Record<string, Function> = {
- get(this: MapTypes, key: unknown) {
- return get(this, key)
- },
- get size() {
- return size((this as unknown) as IterableCollections)
- },
- has,
- add,
- set,
- delete: deleteEntry,
- clear,
- forEach: createForEach(false, false)
- }
- const shallowInstrumentations: Record<string, Function> = {
- get(this: MapTypes, key: unknown) {
- return get(this, key, false, true)
- },
- get size() {
- return size((this as unknown) as IterableCollections)
- },
- has,
- add,
- set,
- delete: deleteEntry,
- clear,
- forEach: createForEach(false, true)
- }
- const readonlyInstrumentations: Record<string, Function> = {
- get(this: MapTypes, key: unknown) {
- return get(this, key, true)
- },
- get size() {
- return size((this as unknown) as IterableCollections, true)
- },
- has(this: MapTypes, key: unknown) {
- return has.call(this, key, true)
- },
- add: createReadonlyMethod(TriggerOpTypes.ADD),
- set: createReadonlyMethod(TriggerOpTypes.SET),
- delete: createReadonlyMethod(TriggerOpTypes.DELETE),
- clear: createReadonlyMethod(TriggerOpTypes.CLEAR),
- forEach: createForEach(true, false)
- }
- const shallowReadonlyInstrumentations: Record<string, Function> = {
- get(this: MapTypes, key: unknown) {
- return get(this, key, true, true)
- },
- get size() {
- return size((this as unknown) as IterableCollections, true)
- },
- has(this: MapTypes, key: unknown) {
- return has.call(this, key, true)
- },
- add: createReadonlyMethod(TriggerOpTypes.ADD),
- set: createReadonlyMethod(TriggerOpTypes.SET),
- delete: createReadonlyMethod(TriggerOpTypes.DELETE),
- clear: createReadonlyMethod(TriggerOpTypes.CLEAR),
- forEach: createForEach(true, true)
- }
- const iteratorMethods = ['keys', 'values', 'entries', Symbol.iterator]
- iteratorMethods.forEach(method => {
- mutableInstrumentations[method as string] = createIterableMethod(
- method,
- false,
- false
- )
- readonlyInstrumentations[method as string] = createIterableMethod(
- method,
- true,
- false
- )
- shallowInstrumentations[method as string] = createIterableMethod(
- method,
- false,
- true
- )
- shallowReadonlyInstrumentations[method as string] = createIterableMethod(
- method,
- true,
- true
- )
- })
- function createInstrumentationGetter(isReadonly: boolean, shallow: boolean) {
- const instrumentations = shallow
- ? isReadonly
- ? shallowReadonlyInstrumentations
- : shallowInstrumentations
- : isReadonly
- ? readonlyInstrumentations
- : mutableInstrumentations
- 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: createInstrumentationGetter(false, false)
- }
- export const shallowCollectionHandlers: ProxyHandler<CollectionTypes> = {
- get: createInstrumentationGetter(false, true)
- }
- export const readonlyCollectionHandlers: ProxyHandler<CollectionTypes> = {
- get: createInstrumentationGetter(true, false)
- }
- export const shallowReadonlyCollectionHandlers: ProxyHandler<
- CollectionTypes
- > = {
- get: 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)
- console.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.`
- )
- }
- }
|