-
-
Notifications
You must be signed in to change notification settings - Fork 8.4k
Commit
- Loading branch information
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -30,40 +30,42 @@ const getProto = <T extends CollectionTypes>(v: T): any => | |
function get( | ||
target: MapTypes, | ||
key: unknown, | ||
wrap: typeof toReactive | typeof toReadonly | typeof toShallow | ||
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) | ||
This comment has been minimized.
Sorry, something went wrong. |
||
const rawKey = toRaw(key) | ||
if (key !== rawKey) { | ||
track(rawTarget, TrackOpTypes.GET, key) | ||
!isReadonly && track(rawTarget, TrackOpTypes.GET, key) | ||
} | ||
track(rawTarget, TrackOpTypes.GET, rawKey) | ||
!isReadonly && track(rawTarget, TrackOpTypes.GET, rawKey) | ||
const { has } = getProto(rawTarget) | ||
This comment has been minimized.
Sorry, something went wrong.
jods4
Contributor
|
||
const wrap = isReadonly ? toReadonly : isShallow ? toShallow : toReactive | ||
if (has.call(rawTarget, key)) { | ||
This comment has been minimized.
Sorry, something went wrong.
jods4
Contributor
|
||
return wrap(target.get(key)) | ||
} else if (has.call(rawTarget, rawKey)) { | ||
This comment has been minimized.
Sorry, something went wrong.
jods4
Contributor
|
||
return wrap(target.get(rawKey)) | ||
} | ||
} | ||
|
||
function has(this: CollectionTypes, key: unknown): boolean { | ||
const target = toRaw(this) | ||
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) { | ||
track(target, TrackOpTypes.HAS, key) | ||
!isReadonly && track(rawTarget, TrackOpTypes.HAS, key) | ||
} | ||
track(target, TrackOpTypes.HAS, rawKey) | ||
const has = getProto(target).has | ||
return has.call(target, key) || has.call(target, rawKey) | ||
!isReadonly && track(rawTarget, TrackOpTypes.HAS, rawKey) | ||
return target.has(key) || target.has(rawKey) | ||
} | ||
|
||
function size(target: IterableCollections) { | ||
target = toRaw(target) | ||
track(target, TrackOpTypes.ITERATE, ITERATE_KEY) | ||
return Reflect.get(getProto(target), 'size', target) | ||
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) { | ||
This comment has been minimized.
Sorry, something went wrong.
jods4
Contributor
|
||
|
@@ -137,15 +139,15 @@ function clear(this: IterableCollections) { | |
return result | ||
} | ||
|
||
function createForEach(isReadonly: boolean, shallow: boolean) { | ||
function createForEach(isReadonly: boolean, isShallow: boolean) { | ||
return function forEach( | ||
this: IterableCollections, | ||
callback: Function, | ||
thisArg?: unknown | ||
) { | ||
const observed = this | ||
const target = toRaw(observed) | ||
const wrap = isReadonly ? toReadonly : shallow ? toShallow : toReactive | ||
const wrap = isReadonly ? toReadonly : isShallow ? toShallow : toReactive | ||
!isReadonly && track(target, TrackOpTypes.ITERATE, ITERATE_KEY) | ||
// important: create sure the callback is | ||
// 1. invoked with the reactive map as `this` and 3rd arg | ||
|
@@ -173,19 +175,19 @@ interface IterationResult { | |
function createIterableMethod( | ||
method: string | symbol, | ||
isReadonly: boolean, | ||
shallow: boolean | ||
isShallow: boolean | ||
) { | ||
return function( | ||
this: IterableCollections, | ||
...args: unknown[] | ||
): Iterable & Iterator { | ||
const target = (this as any)[ReactiveFlags.RAW] | ||
const rawTarget = toRaw(this) | ||
const rawTarget = toRaw(target) | ||
const isMap = rawTarget instanceof Map | ||
const isPair = method === 'entries' || (method === Symbol.iterator && isMap) | ||
const isKeyOnly = method === 'keys' && isMap | ||
const innerIterator = target[method](...args) | ||
const wrap = isReadonly ? toReadonly : shallow ? toShallow : toReactive | ||
const wrap = isReadonly ? toReadonly : isShallow ? toShallow : toReactive | ||
!isReadonly && | ||
track( | ||
rawTarget, | ||
|
@@ -228,7 +230,7 @@ function createReadonlyMethod(type: TriggerOpTypes): Function { | |
|
||
const mutableInstrumentations: Record<string, Function> = { | ||
get(this: MapTypes, key: unknown) { | ||
return get(this, key, toReactive) | ||
return get(this, key) | ||
}, | ||
get size() { | ||
return size((this as unknown) as IterableCollections) | ||
|
@@ -243,7 +245,7 @@ const mutableInstrumentations: Record<string, Function> = { | |
|
||
const shallowInstrumentations: Record<string, Function> = { | ||
get(this: MapTypes, key: unknown) { | ||
return get(this, key, toShallow) | ||
return get(this, key, false, true) | ||
}, | ||
get size() { | ||
return size((this as unknown) as IterableCollections) | ||
|
@@ -258,12 +260,14 @@ const shallowInstrumentations: Record<string, Function> = { | |
|
||
const readonlyInstrumentations: Record<string, Function> = { | ||
get(this: MapTypes, key: unknown) { | ||
return get(this, key, toReadonly) | ||
return get(this, key, true) | ||
}, | ||
get size() { | ||
return size((this as unknown) as IterableCollections) | ||
return size((this as unknown) as IterableCollections, true) | ||
}, | ||
has(this: MapTypes, key: unknown) { | ||
return has.call(this, key, true) | ||
}, | ||
has, | ||
add: createReadonlyMethod(TriggerOpTypes.ADD), | ||
set: createReadonlyMethod(TriggerOpTypes.SET), | ||
delete: createReadonlyMethod(TriggerOpTypes.DELETE), | ||
|
3 comments
on commit 50adc01
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this is mostly a good change, but it should be pointed out as a breaking change in the next release notes.
Previously wrapping the raw collection with readonly would work as a reactive object.
I'm saying "mostly" and I believe some people may have wrapped the raw collection because of a single drawback: it may seem more efficient to wrap the collection directly rather than stacking a proxy on top of another proxy.
Which makes me wonder: wouldn't it be more efficient to implement this behavior by proxying directly the raw collection? You could still use a different proxy if it was wrapping a reactive proxy (track) or not (don't track).
Speaking of efficiency, don't you think that at this point it would be better to split collections handlers completely? It would simplify both implementation as the (non-tracking) readonly handlers are now drastically simpler. Unless I'm wrong:
has
,size
andforeach
don't need a proxy trap anymore, they can just passthrough.get
is massively simpler: (a) try an unwrapped key + (b) returntoReadonly(value)
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
After giving it more thinking, I changed my mind about the unwrapping of keys in a reactive/readonly Map or Set.
TL;DR: I think reactive collections should not unwrap keys, they should work the same way normal collections would (minus the reactivity)
Yes, proxies create referential problems and we know them well (==
, Array:includes
, Map
, Set
, etc.).
That's bad but at least we can easily explain it.
In a plain Map
, a reactive collection would be a different key than a raw collection.
There's nothing we can do about that, it's how JS works.
That a reactive Map would work differently only adds to the confusion.
Reactive collections should work like their standard counterparts.
It's also dangerous.
When refactoring code, one should be able to replace a reactive collection by a plain one, and vice-versa, without having to fear the introduction of bugs.
With this automatic unwrapping, changing a collection to/from reactive has the potential to merge/split keys and create unexpected changes in behavior.
Similarly, when writing generic library code, we shouldn't have to ask ourselves if the Map we take as a parameter is reactive or not.
It's behavior should be clear: it's a Map and it works like one.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@yyx990803 did you see my comments in this commit?
Most of it is minor tweaks, but I think the ones impacting public API should be changed before RTM.
In particular:
Set::add()
andMap::set()
break reactivity when chained.- Reactive collections behave differently from plain collections when their key is a reactive object.
And maybe:
- Reactive collections don't invoke "overriden" methods on instances.
You're doing a double
toRaw
here, because it might be a readonly on top of reactive on top of Map.That fixes
get
, but isn'ttoRaw
globally flawed now (because it only undoes one level)?For instance, the
const rawKey = toRaw(key)
isn't gonna find the key if I do the following, is that expected/intended?