| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249 |
- import { toRaw, reactive, readonly } from './reactive'
- import { track, trigger, ITERATE_KEY } from './effect'
- import { TrackOpTypes, TriggerOpTypes } from './operations'
- import { LOCKED } from './lock'
- import { isObject, capitalize, hasOwn, hasChanged } 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) : value
- const getProto = <T extends CollectionTypes>(v: T): any =>
- Reflect.getPrototypeOf(v)
- function get(
- target: MapTypes,
- key: unknown,
- wrap: typeof toReactive | typeof toReadonly
- ) {
- target = toRaw(target)
- const rawKey = toRaw(key)
- track(target, TrackOpTypes.GET, rawKey)
- const { has, get } = getProto(target)
- if (has.call(target, key)) {
- return wrap(get.call(target, key))
- } else if (has.call(target, rawKey)) {
- return wrap(get.call(target, rawKey))
- }
- }
- function has(this: CollectionTypes, key: unknown): boolean {
- const target = toRaw(this)
- const rawKey = toRaw(key)
- track(target, TrackOpTypes.HAS, rawKey)
- const has = getProto(target).has
- return has.call(target, key) || has.call(target, rawKey)
- }
- function size(target: IterableCollections) {
- target = toRaw(target)
- track(target, TrackOpTypes.ITERATE, ITERATE_KEY)
- return Reflect.get(getProto(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)
- const result = proto.add.call(target, value)
- if (!hadKey) {
- trigger(target, TriggerOpTypes.ADD, value, value)
- }
- return result
- }
- function set(this: MapTypes, key: unknown, value: unknown) {
- value = toRaw(value)
- key = toRaw(key)
- const target = toRaw(this)
- const proto = getProto(target)
- const hadKey = proto.has.call(target, key)
- const oldValue = proto.get.call(target, key)
- const result = proto.set.call(target, key, value)
- if (!hadKey) {
- trigger(target, TriggerOpTypes.ADD, key, value)
- } else if (hasChanged(value, oldValue)) {
- trigger(target, TriggerOpTypes.SET, key, value, oldValue)
- }
- return result
- }
- function deleteEntry(this: CollectionTypes, key: unknown) {
- const target = toRaw(this)
- const { has, get, delete: del } = getProto(target)
- let hadKey = has.call(target, key)
- if (!hadKey) {
- key = toRaw(key)
- hadKey = has.call(target, key)
- }
- const oldValue = get ? get.call(target, key) : undefined
- // forward the operation before queueing reactions
- const result = del.call(target, 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__
- ? target instanceof Map
- ? new Map(target)
- : new Set(target)
- : undefined
- // forward the operation before queueing reactions
- const result = getProto(target).clear.call(target)
- if (hadItems) {
- trigger(target, TriggerOpTypes.CLEAR, undefined, undefined, oldTarget)
- }
- return result
- }
- function createForEach(isReadonly: boolean) {
- return function forEach(
- this: IterableCollections,
- callback: Function,
- thisArg?: unknown
- ) {
- const observed = this
- const target = toRaw(observed)
- const wrap = isReadonly ? toReadonly : toReactive
- track(target, TrackOpTypes.ITERATE, ITERATE_KEY)
- // important: create 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.
- function wrappedCallback(value: unknown, key: unknown) {
- return callback.call(observed, wrap(value), wrap(key), observed)
- }
- return getProto(target).forEach.call(target, wrappedCallback, thisArg)
- }
- }
- function createIterableMethod(method: string | symbol, isReadonly: boolean) {
- return function(this: IterableCollections, ...args: unknown[]) {
- const target = toRaw(this)
- const isPair =
- method === 'entries' ||
- (method === Symbol.iterator && target instanceof Map)
- const innerIterator = getProto(target)[method].apply(target, args)
- const wrap = isReadonly ? toReadonly : toReactive
- track(target, TrackOpTypes.ITERATE, 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(
- method: Function,
- type: TriggerOpTypes
- ): Function {
- return function(this: CollectionTypes, ...args: unknown[]) {
- if (LOCKED) {
- 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
- } else {
- return method.apply(this, args)
- }
- }
- }
- const mutableInstrumentations: Record<string, Function> = {
- get(this: MapTypes, key: unknown) {
- return get(this, key, toReactive)
- },
- get size() {
- return size((this as unknown) as IterableCollections)
- },
- has,
- add,
- set,
- delete: deleteEntry,
- clear,
- forEach: createForEach(false)
- }
- const readonlyInstrumentations: Record<string, Function> = {
- get(this: MapTypes, key: unknown) {
- return get(this, key, toReadonly)
- },
- get size(this: IterableCollections) {
- return size(this)
- },
- has,
- add: createReadonlyMethod(add, TriggerOpTypes.ADD),
- set: createReadonlyMethod(set, TriggerOpTypes.SET),
- delete: createReadonlyMethod(deleteEntry, TriggerOpTypes.DELETE),
- clear: createReadonlyMethod(clear, TriggerOpTypes.CLEAR),
- forEach: createForEach(true)
- }
- const iteratorMethods = ['keys', 'values', 'entries', Symbol.iterator]
- iteratorMethods.forEach(method => {
- mutableInstrumentations[method as string] = createIterableMethod(
- method,
- false
- )
- readonlyInstrumentations[method as string] = createIterableMethod(
- method,
- true
- )
- })
- function createInstrumentationGetter(
- instrumentations: Record<string, Function>
- ) {
- return (
- target: CollectionTypes,
- key: string | symbol,
- receiver: CollectionTypes
- ) =>
- Reflect.get(
- hasOwn(instrumentations, key) && key in target
- ? instrumentations
- : target,
- key,
- receiver
- )
- }
- export const mutableCollectionHandlers: ProxyHandler<CollectionTypes> = {
- get: createInstrumentationGetter(mutableInstrumentations)
- }
- export const readonlyCollectionHandlers: ProxyHandler<CollectionTypes> = {
- get: createInstrumentationGetter(readonlyInstrumentations)
- }
|