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'
|
} from './reactive'
|
||||||
import { ITERATE_KEY, MAP_KEY_ITERATE_KEY, track, trigger } from './dep'
|
import { ITERATE_KEY, MAP_KEY_ITERATE_KEY, track, trigger } from './dep'
|
||||||
import { ReactiveFlags, TrackOpTypes, TriggerOpTypes } from './constants'
|
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'
|
import { warn } from './warning'
|
||||||
|
|
||||||
type CollectionTypes = IterableCollections | WeakCollections
|
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 =>
|
const getProto = <T extends CollectionTypes>(v: T): any =>
|
||||||
Reflect.getPrototypeOf(v)
|
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(
|
function createIterableMethod(
|
||||||
method: string | symbol,
|
method: string | symbol,
|
||||||
isReadonly: boolean,
|
isReadonly: boolean,
|
||||||
|
@ -232,74 +93,158 @@ function createReadonlyMethod(type: TriggerOpTypes): Function {
|
||||||
|
|
||||||
type Instrumentations = Record<string | symbol, Function | number>
|
type Instrumentations = Record<string | symbol, Function | number>
|
||||||
|
|
||||||
function createInstrumentations() {
|
function createInstrumentations(
|
||||||
const mutableInstrumentations: Instrumentations = {
|
readonly: boolean,
|
||||||
|
shallow: boolean,
|
||||||
|
): Instrumentations {
|
||||||
|
const instrumentations: Instrumentations = {
|
||||||
get(this: MapTypes, key: unknown) {
|
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() {
|
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 = {
|
extend(
|
||||||
get(this: MapTypes, key: unknown) {
|
instrumentations,
|
||||||
return get(this, key, false, true)
|
readonly
|
||||||
},
|
? {
|
||||||
get size() {
|
add: createReadonlyMethod(TriggerOpTypes.ADD),
|
||||||
return size(this as unknown as IterableCollections)
|
set: createReadonlyMethod(TriggerOpTypes.SET),
|
||||||
},
|
delete: createReadonlyMethod(TriggerOpTypes.DELETE),
|
||||||
has,
|
clear: createReadonlyMethod(TriggerOpTypes.CLEAR),
|
||||||
add(this: SetTypes, value: unknown) {
|
}
|
||||||
return add.call(this, value, true)
|
: {
|
||||||
},
|
add(this: SetTypes, value: unknown) {
|
||||||
set(this: MapTypes, key: unknown, value: unknown) {
|
if (!shallow && !isShallow(value) && !isReadonly(value)) {
|
||||||
return set.call(this, key, value, true)
|
value = toRaw(value)
|
||||||
},
|
}
|
||||||
delete: deleteEntry,
|
const target = toRaw(this)
|
||||||
clear,
|
const proto = getProto(target)
|
||||||
forEach: createForEach(false, true),
|
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 = {
|
let hadKey = has.call(target, key)
|
||||||
get(this: MapTypes, key: unknown) {
|
if (!hadKey) {
|
||||||
return get(this, key, true)
|
key = toRaw(key)
|
||||||
},
|
hadKey = has.call(target, key)
|
||||||
get size() {
|
} else if (__DEV__) {
|
||||||
return size(this as unknown as IterableCollections, true)
|
checkIdentityKeys(target, has, key)
|
||||||
},
|
}
|
||||||
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: Instrumentations = {
|
const oldValue = get.call(target, key)
|
||||||
get(this: MapTypes, key: unknown) {
|
target.set(key, value)
|
||||||
return get(this, key, true, true)
|
if (!hadKey) {
|
||||||
},
|
trigger(target, TriggerOpTypes.ADD, key, value)
|
||||||
get size() {
|
} else if (hasChanged(value, oldValue)) {
|
||||||
return size(this as unknown as IterableCollections, true)
|
trigger(target, TriggerOpTypes.SET, key, value, oldValue)
|
||||||
},
|
}
|
||||||
has(this: MapTypes, key: unknown) {
|
return this
|
||||||
return has.call(this, key, true)
|
},
|
||||||
},
|
delete(this: CollectionTypes, key: unknown) {
|
||||||
add: createReadonlyMethod(TriggerOpTypes.ADD),
|
const target = toRaw(this)
|
||||||
set: createReadonlyMethod(TriggerOpTypes.SET),
|
const { has, get } = getProto(target)
|
||||||
delete: createReadonlyMethod(TriggerOpTypes.DELETE),
|
let hadKey = has.call(target, key)
|
||||||
clear: createReadonlyMethod(TriggerOpTypes.CLEAR),
|
if (!hadKey) {
|
||||||
forEach: createForEach(true, true),
|
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 = [
|
const iteratorMethods = [
|
||||||
'keys',
|
'keys',
|
||||||
|
@ -309,39 +254,14 @@ function createInstrumentations() {
|
||||||
] as const
|
] as const
|
||||||
|
|
||||||
iteratorMethods.forEach(method => {
|
iteratorMethods.forEach(method => {
|
||||||
mutableInstrumentations[method] = createIterableMethod(method, false, false)
|
instrumentations[method] = createIterableMethod(method, readonly, shallow)
|
||||||
readonlyInstrumentations[method] = createIterableMethod(method, true, false)
|
|
||||||
shallowInstrumentations[method] = createIterableMethod(method, false, true)
|
|
||||||
shallowReadonlyInstrumentations[method] = createIterableMethod(
|
|
||||||
method,
|
|
||||||
true,
|
|
||||||
true,
|
|
||||||
)
|
|
||||||
})
|
})
|
||||||
|
|
||||||
return [
|
return instrumentations
|
||||||
mutableInstrumentations,
|
|
||||||
readonlyInstrumentations,
|
|
||||||
shallowInstrumentations,
|
|
||||||
shallowReadonlyInstrumentations,
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const [
|
|
||||||
mutableInstrumentations,
|
|
||||||
readonlyInstrumentations,
|
|
||||||
shallowInstrumentations,
|
|
||||||
shallowReadonlyInstrumentations,
|
|
||||||
] = /* @__PURE__*/ createInstrumentations()
|
|
||||||
|
|
||||||
function createInstrumentationGetter(isReadonly: boolean, shallow: boolean) {
|
function createInstrumentationGetter(isReadonly: boolean, shallow: boolean) {
|
||||||
const instrumentations = shallow
|
const instrumentations = createInstrumentations(isReadonly, shallow)
|
||||||
? isReadonly
|
|
||||||
? shallowReadonlyInstrumentations
|
|
||||||
: shallowInstrumentations
|
|
||||||
: isReadonly
|
|
||||||
? readonlyInstrumentations
|
|
||||||
: mutableInstrumentations
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
target: CollectionTypes,
|
target: CollectionTypes,
|
||||||
|
|
Loading…
Reference in New Issue