mirror of https://github.com/vuejs/core.git
refactor(reactivity): reduce size of collectionHandlers (#12152)
This commit is contained in:
parent
ea943afe40
commit
c82b66214b
|
@ -8,7 +8,14 @@ import {
|
|||
} from './reactive'
|
||||
import { ITERATE_KEY, MAP_KEY_ITERATE_KEY, track, trigger } from './dep'
|
||||
import { ReactiveFlags, TrackOpTypes, TriggerOpTypes } from './constants'
|
||||
import { capitalize, hasChanged, hasOwn, isMap, toRawType } from '@vue/shared'
|
||||
import {
|
||||
capitalize,
|
||||
extend,
|
||||
hasChanged,
|
||||
hasOwn,
|
||||
isMap,
|
||||
toRawType,
|
||||
} from '@vue/shared'
|
||||
import { warn } from './warning'
|
||||
|
||||
type CollectionTypes = IterableCollections | WeakCollections
|
||||
|
@ -23,152 +30,6 @@ 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[ReactiveFlags.RAW]
|
||||
const rawTarget = toRaw(target)
|
||||
const rawKey = toRaw(key)
|
||||
if (!isReadonly) {
|
||||
if (hasChanged(key, rawKey)) {
|
||||
track(rawTarget, TrackOpTypes.GET, key)
|
||||
}
|
||||
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[ReactiveFlags.RAW]
|
||||
const rawTarget = toRaw(target)
|
||||
const rawKey = toRaw(key)
|
||||
if (!isReadonly) {
|
||||
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)
|
||||
}
|
||||
|
||||
function size(target: IterableCollections, isReadonly = false) {
|
||||
target = target[ReactiveFlags.RAW]
|
||||
!isReadonly && track(toRaw(target), TrackOpTypes.ITERATE, ITERATE_KEY)
|
||||
return Reflect.get(target, 'size', target)
|
||||
}
|
||||
|
||||
function add(this: SetTypes, value: unknown, _isShallow = false) {
|
||||
if (!_isShallow && !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
|
||||
}
|
||||
|
||||
function set(this: MapTypes, key: unknown, value: unknown, _isShallow = false) {
|
||||
if (!_isShallow && !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
|
||||
}
|
||||
|
||||
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
|
||||
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)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
function createIterableMethod(
|
||||
method: string | symbol,
|
||||
isReadonly: boolean,
|
||||
|
@ -232,74 +93,158 @@ function createReadonlyMethod(type: TriggerOpTypes): Function {
|
|||
|
||||
type Instrumentations = Record<string | symbol, Function | number>
|
||||
|
||||
function createInstrumentations() {
|
||||
const mutableInstrumentations: Instrumentations = {
|
||||
function createInstrumentations(
|
||||
readonly: boolean,
|
||||
shallow: boolean,
|
||||
): Instrumentations {
|
||||
const instrumentations: Instrumentations = {
|
||||
get(this: MapTypes, key: unknown) {
|
||||
return get(this, key)
|
||||
// #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() {
|
||||
return size(this as unknown as IterableCollections)
|
||||
const target = (this as unknown as IterableCollections)[ReactiveFlags.RAW]
|
||||
!readonly && track(toRaw(target), TrackOpTypes.ITERATE, ITERATE_KEY)
|
||||
return Reflect.get(target, 'size', target)
|
||||
},
|
||||
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)
|
||||
})
|
||||
},
|
||||
has,
|
||||
add,
|
||||
set,
|
||||
delete: deleteEntry,
|
||||
clear,
|
||||
forEach: createForEach(false, false),
|
||||
}
|
||||
|
||||
const shallowInstrumentations: Instrumentations = {
|
||||
get(this: MapTypes, key: unknown) {
|
||||
return get(this, key, false, true)
|
||||
},
|
||||
get size() {
|
||||
return size(this as unknown as IterableCollections)
|
||||
},
|
||||
has,
|
||||
add(this: SetTypes, value: unknown) {
|
||||
return add.call(this, value, true)
|
||||
},
|
||||
set(this: MapTypes, key: unknown, value: unknown) {
|
||||
return set.call(this, key, value, true)
|
||||
},
|
||||
delete: deleteEntry,
|
||||
clear,
|
||||
forEach: createForEach(false, true),
|
||||
}
|
||||
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)
|
||||
|
||||
const readonlyInstrumentations: Instrumentations = {
|
||||
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),
|
||||
}
|
||||
let hadKey = has.call(target, key)
|
||||
if (!hadKey) {
|
||||
key = toRaw(key)
|
||||
hadKey = has.call(target, key)
|
||||
} else if (__DEV__) {
|
||||
checkIdentityKeys(target, has, key)
|
||||
}
|
||||
|
||||
const shallowReadonlyInstrumentations: Instrumentations = {
|
||||
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 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',
|
||||
|
@ -309,39 +254,14 @@ function createInstrumentations() {
|
|||
] as const
|
||||
|
||||
iteratorMethods.forEach(method => {
|
||||
mutableInstrumentations[method] = createIterableMethod(method, false, false)
|
||||
readonlyInstrumentations[method] = createIterableMethod(method, true, false)
|
||||
shallowInstrumentations[method] = createIterableMethod(method, false, true)
|
||||
shallowReadonlyInstrumentations[method] = createIterableMethod(
|
||||
method,
|
||||
true,
|
||||
true,
|
||||
)
|
||||
instrumentations[method] = createIterableMethod(method, readonly, shallow)
|
||||
})
|
||||
|
||||
return [
|
||||
mutableInstrumentations,
|
||||
readonlyInstrumentations,
|
||||
shallowInstrumentations,
|
||||
shallowReadonlyInstrumentations,
|
||||
]
|
||||
return instrumentations
|
||||
}
|
||||
|
||||
const [
|
||||
mutableInstrumentations,
|
||||
readonlyInstrumentations,
|
||||
shallowInstrumentations,
|
||||
shallowReadonlyInstrumentations,
|
||||
] = /* @__PURE__*/ createInstrumentations()
|
||||
|
||||
function createInstrumentationGetter(isReadonly: boolean, shallow: boolean) {
|
||||
const instrumentations = shallow
|
||||
? isReadonly
|
||||
? shallowReadonlyInstrumentations
|
||||
: shallowInstrumentations
|
||||
: isReadonly
|
||||
? readonlyInstrumentations
|
||||
: mutableInstrumentations
|
||||
const instrumentations = createInstrumentations(isReadonly, shallow)
|
||||
|
||||
return (
|
||||
target: CollectionTypes,
|
||||
|
|
Loading…
Reference in New Issue