-
-
Notifications
You must be signed in to change notification settings - Fork 8.3k
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): some mutation methods of Array cause infinite recursion #2138
Conversation
@@ -49,6 +55,14 @@ const arrayInstrumentations: Record<string, Function> = {} | |||
} | |||
}) | |||
|
|||
const arrayShouldSkipTrackingLength = new Set([ |
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.
this name doesn't make sence and I think we should use makeMap
instead of new Set
here.
I updated the implementation to use static instrumentations in the same map (this is more efficient than creating a new function on every access). |
I am a bit confused by this change, a more common use-case is broken now: setup() {
const arr = ref([])
watchEffect(() => {
console.log('Array: ', arr.value)
})
arr.value.push('test') // doesn't log
return { arr }
} |
I believe that by fixing the logic of these mutational effects it could be possible to solve the issue without affecting valid use-cases like above: const arr = reactive([])
watchEffect(()=>{
if (arr.includes(1)) return
arr.push(1)
})
watchEffect(()=>{
if (arr.includes(2)) return
arr.push(2)
}) It is unclear why these effects should stop after the first run. |
if we want arr = [1,1,1,1,1] ,what should we do ? |
It depends on the logic that describes why it should call |
tested on rc.11 and rc.12, failed too |
It works in Vue 2: https://jsfiddle.net/7bahj9su/ This change significantly diminishes profits of the new reactivity. While before you have had to use watchEffect(() => {
arr.value.length; // register a dependency
console.log(`array: `, arr.value);
}) |
@CyberAP I don't think your issue is related to this PR. For JSFiddle example, it looks like a bug, and it also existed in previous RC versions. For watchEffect example, it is expected behavior, since you only accessed const arr = reactive([])
watch(arr, () => {
console.log('Array: ', arr) // works as you expected.
}) |
@yangmingshan It's not a bug cuz these platforms rewrite |
@CyberAP IMO it's expected cuz only |
It is not the expected behaviour. When I am logging Your example doesn't work with |
@CyberAP For const arr = ref([])
watch(
arr,
() => {
console.log('Array: ', arr) // it will log after ref value changed or array changed.
},
{ deep: true }
) |
But what if I don't need deep reactivity here? (An array of nested arrays\objects?) |
@CyberAP could you please check again? if you try old version on online js runner such as codepen, they rewrite that means it's not related to this PR but reactivity of vue 3 |
I suggest to open a new issue, but it's not related to this PR. |
For the option breaking change I've made a separate issue: #2175 |
so it's really unexpected for me that many are so eager to give reaction even before knowing the situation :) |
Not sure I follow, I've tested this locally in a plain html file and it doesn't work either. I don't understand how patching console.log is related here (you can put any effect the depends on array there). |
@CyberAP I've mistakenly thought you were testing the old version of vue 3 with platforms like codepen and latest version locally, so I said that. Anyway, what I mean is that it has nothing to do with this PR. Furthermore, I think this inconsistency is caused by the new reactivity system. |
This is directly related to this PR. I would like to be able to have a reactive effect from pushing to a reactive array the same way it worked in Vue 2, this PR disables that. Deep watching an array, replacing the whole array or subscribing to an array length are not the same in terms of developer experience as simply |
so how do you like to solve such an infinite recursion and this PR ONLY disabled not triggering but tracking keys like |
and also I've already tested this on versions from |
你俩都比较轴,不是提了新的PR了嘛,尤大肯定会处理的。耐心等待。哈哈😊 |
哈哈,确实有点杠上了,不过你看一下新的issue里的链接,尤大说是有意的 |
哈哈 😄 方便留个微信吗,学习中有不懂的互相交流。 |
@CyberAP You got it wrong, like @unbyte said this PR is only about not tracking For now, watchEffect will only track fields that you accessed inside it, I think it designed for simple use case. As for your use case try watch instead. const arr = ref([])
// if you only care about length
watch(
() => arr.value.length,
() => {
console.log('Array: ', arr)
}
) |
already mail to u. |
@yangmingshan the point stays the same. If you're pushing to a reactive array why this should be discarded? The whole point of mutating reactive value is to trigger reactivity. How's pushing to an array within an effect is different from pushing to an array outside of effect? |
It's not about pushing itself, pushing will works as it should be, push a new item to an array will still trigger effect run no matter inside effect or not. It's about unexpected tracking. const arr = reactive([])
watchEffect(() => {
arr.push(1) // this effect will run whenever the length of arr changes (before this PR), it shouldn't.
}) |
Why else it would have to trigger then? The code itself is recursive: an effect that mutates its own dependency. It is recursive in Vue 2 and I don't see any reason why it shouldn't be that way in Vue 3. |
example 1 const arr = reactive([])
watchEffect(()=>{
arr.push(1)
})
watchEffect(()=>{
arr.push(2)
}) You know example 1 just want a example 2 const arr = reactive([])
watchEffect(()=>{
arr.length++
})
watchEffect(()=>{
arr.length++
}) With example 2, the features of Keywords: obvious dependency. |
The situation looks similar to #3653. I'm also not convinced that it should be fixed. I don't think this is a common use case. I think this PR should be revert and pending for not getting enough feedback of real world use case. I'm deeply worried that this PR can't be reverted for causing breaking change in the future. @yyx990803 |
@iheyunfei Without this patch, you can't even invoke any statement that executes Array.push in the render function, including |
Array.prototype.push
,Array.prototype.pop
,Array.prototype.unshift
,Array.prototype.shift
all access.length
, which leads to an infinite recursion in some usage scenarios.e.g.
close #2137