Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(reactivity): shallowReactive map "unwraps" the nested refs #8503

Merged
merged 5 commits into from
Jul 16, 2024

Conversation

liuseen-l
Copy link
Contributor

@liuseen-l liuseen-l commented Jun 5, 2023

[BREAKING CHANGE]

fix #11249
fix #8501
closes #8502

although pr #8502 fix #8501 issue, it is a destructive fix for shallowReactive, so i take a relatively elegant approach to the issue. At the same time, there are also the same issues with Set, so they have been modified together

@sxzz sxzz added the ready for review This PR requires more reviews label Aug 14, 2023
@sxzz
Copy link
Member

sxzz commented Aug 24, 2023

/ecosystem-ci run

@vue-bot
Copy link
Contributor

vue-bot commented Aug 24, 2023

📝 Ran ecosystem CI: Open

suite result
nuxt ✅ success
pinia ✅ success
quasar ❌ failure
router ✅ success
test-utils ✅ success
vant ✅ success
vite-plugin-vue ✅ success
vitepress ✅ success
vue-i18n ✅ success
vue-macros ❌ failure
vuetify ✅ success
vueuse ✅ success
vue-simple-compiler ✅ success

@github-actions
Copy link

github-actions bot commented Aug 24, 2023

Size Report

Bundles

File Size Gzip Brotli
runtime-dom.global.prod.js 89.8 kB (+106 B) 34.3 kB (+28 B) 31 kB (+14 B)
vue.global.prod.js 146 kB (+106 B) 53.8 kB (+37 B) 48 kB (+40 B)

Usages

Name Size Gzip Brotli
createApp 49.7 kB (+106 B) 19.5 kB (+41 B) 17.8 kB (+38 B)
createSSRApp 53.2 kB (+106 B) 21 kB (+46 B) 19.1 kB (+32 B)
defineCustomElement 52 kB (+106 B) 20.2 kB (+43 B) 18.5 kB (+41 B)
overall 63.2 kB (+106 B) 24.5 kB (+46 B) 22.3 kB (+65 B)

@jacekkarczmarczyk
Copy link
Contributor

there are also the same issues with Set

Shouldn't there be a test for Set as well in that case?

Copy link
Contributor

@skirtles-code skirtles-code left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is currently marked as a breaking change. I don't think it is a breaking change, not in the semantic versioning sense.

That said, it could break people's applications if they're relying on the buggy behaviour. I think it'd be appropriate for it to go in a minor release.

The current behaviour on main leads to some strange inconsistencies. e.g.:

TS gets confused about what's going on because it's expecting the ref to still be unwrapped for entry c.


As suggested previously, I think a test should be added to this PR for the Set case too.

I've also made a suggestion about the specifics of the implementation below.

@@ -66,8 +66,8 @@ function size(target: IterableCollections, isReadonly = false) {
return Reflect.get(target, 'size', target)
}

function add(this: SetTypes, value: unknown) {
value = toRaw(value)
function add(this: SetTypes, value: unknown, isShallow = false) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Adding a boolean flag to a function is generally something that sets off alarm bells for me. There's a lot of stuff been written on this topic, e.g. https://martinfowler.com/bliki/FlagArgument.html, but that isn't to say it's necessarily wrong.

When I first looked over this PR a few weeks ago, I felt like the boolean flag was justified here. But it kept nagging away at me, and I'm now wondering whether there's a better way.

First, I'd like to consider this example:

It's a silly example, but it shows how the flag can be passed accidentally by client code. In that example, the index is being passed by forEach as the isShallow value.

Does this matter? Maybe not, but it motivated me to ponder alternative implementations.

I'm now wondering whether this might be a better way to implement it:

  1. Remove the call to toRaw in add and set. Have them effectively be shallow by default.

  2. Override the calls to add and set in mutableInstrumentations to include the toRaw call. Something like:

    add(this: SetTypes, value: unknown) {
      return add.call(this, toRaw(value))
    },
    set(this: MapTypes, key: unknown, value: unknown) {
      return set.call(this, key, toRaw(value))
    },

This avoids the need for the boolean flag. I tried this locally and it seemed to pass the test cases.

Copy link
Contributor Author

@liuseen-l liuseen-l Jun 28, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Method 1 I don't think is advisable, it will lead to the contamination of the raw data, as shown in the code below, modifying the state through the raw data will cause the effect to be re-executed, and toRaw is used to avoid this

 const m = new Map()
 const o1 = reactive(m)
 const o2 = reactive(new Map())
 o1.set('o2', o2)
 effect(() => {
 console.log(m.get('o2').size)
 })
 m.get('o2').set('foo', 1)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think you misunderstood what I meant. I wasn't proposing two separate methods for fixing the problem, those were two parts of a single method.

I've put together an example to show the changes I had in mind:

skirtles-code@073ab1c

The calls to toRaw are still present, they're just moved.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Haha, I'm sorry, I misunderstood

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think adding the isShallow arg is fine in this case because it is consistent with how other functions in the same file are structured. The isShallow flag is used only for value unwrapping in add and set, but affects more things in other functions - in general, these handlers have to be aware of the context they are called in.

@liuseen-l
Copy link
Contributor Author

@skirtles-code @jacekkarczmarczyk thanks for your suggestions. The test case for set has been added.

@yyx990803
Copy link
Member

/ecosystem-ci run

@vue-bot
Copy link
Contributor

vue-bot commented Jul 16, 2024

📝 Ran ecosystem CI: Open

suite result latest scheduled
language-tools failure failure
nuxt success success
pinia success success
primevue success success
quasar success success
radix-vue success success
router success success
test-utils success success
vant success success
vite-plugin-vue success success
vitepress success success
vue-i18n success success
vue-macros success success
vuetify success success
vueuse success success
vue-simple-compiler success success

@yyx990803
Copy link
Member

Merging this as this is indeed a correctness fix and passes Ecosystem CI

@yyx990803 yyx990803 merged commit 50ddafe into vuejs:main Jul 16, 2024
11 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
ready for review This PR requires more reviews scope: reactivity
Projects
7 participants