Open Vanilagy opened 1 year ago
Workarounds for I've found for this are:
const set = reactive(new Set(items.map(toRaw)))
const set = reactive(new Set())
for (const item of items) set.add(item)
The cause of the problem is as follows:
The reactive system proxy the set.add
, if a reactive object is add, its raw value will be stored. see https://github.com/vuejs/core/blob/c82b66214b2d78d669546ce715c8c8f9868a6980/packages/reactivity/src/collectionHandlers.ts#L170
However, during initialization, reactive(new Set([thing]))
does not store the raw value of thing
, but rather the reactive object.
Perhaps we can handle the initial value of the Set
before https://github.com/vuejs/core/blob/c97bb84d0b0a16b012f886b6498e924415ed63e5/packages/reactivity/src/reactive.ts#L292
If the initial value of the Set is a reactive object, its raw value should be stored. align with set.add
. but this may be a breaking change.
Additionally, Map
has the same issue. Maybe consider WeakSet
and WeakMap
as well.
Good analysis!
I don't know to which degree you can consider the fix to be a breaking change. I consider this to be a bug, so patching the bug may be breaking behavior technically, but it breaks the bug, which is a good thing in my eye.
Vue version
@9f8e98a
Link to minimal reproduction
https://play.vuejs.org/#eNp9U01vozAQ/StTXwJSRA699UtqVzl0D+1qmz2t9+DCENyFMWsbkmyU/96xCbSR2koc8Js34/cew17ctm3WdyguxJXLrW49OPRdeyNJN62xHvZgUeVe9wgHKK1pYMb8mSRJuSHnwVea1nA90ZL9IR1rPOt9hXADT+iT37HnT8o8ST1aXe4YvvfY/CL9r0NC5xLunQ/D00tYLGBVacdnazYutI1I2REPNwRxjsbAUR50CYpA80hgkqpZQ7EDTaCCqDmoogiytYfCoKOZh7xStEbuxsDIhuma7+KbAqjJIxVY8NwCcNti7vnwjJXqtbFg+MLYKOlU0mfWgrYU9pIgiA1YVimXRPiIA9QcoNP/8Q5LY5GzDLQAXA71cGQrQ9dbTK4yXV3Aw+OK7bHgXcyRXTvYILBinCI5xsa2ng3DMbxo9xgdR3aM5FTRbenRngoaCNHMRDi7ZspkIB0+IIRFWFprbCIFxwNxW6ziiIHDrBUneyZFGk0eJPFztRjW80bMhXfML/U6e3GGeHNjVlLkpml1jfaxDeE7KS7GFKVQdW023yPmbYfzEc8rzP9+gL+4bcCk+GHRoe1RiqnmlV1zGrG8fHrALb9PxcYUXc3sL4o/0Zm6CxoH2l1HBct+x4tq7+P/x59t5ZZbXj03mgpCYzCRLwX/jt++sP4m9zw7HwMVh1eqVVu4
Steps to reproduce
Simply open the console, you should see an error.
What is expected?
Sets are expected by the user of the language to behave a certain way, different from arrays. Specifically, they differ in this core aspect:
Pushing an item into an array changes the array, regardless of if the item was already in the array. Adding an item to a set changes the set if and only if the item was not already in the set.
What is actually happening?
Vue violates this behavior. It seems to get tripped up as the Set is being initilaized with an object already inside it. When
.add
is called, it first unproxies the object before adding it to the Set. I do not understand this behavior and I think it's counterintuitive. The thing that trips me up in this case is that.has
still returns true here. So,.has
returns true, yet.add
changes the set. Makes no sense.Either
.has
should return false here, or, the better solution,.add
should not change the set.System Info
Any additional comments?
Ran into this bug while replacing a bunch of "if not includes, push item" logic by switching to Set.