在使用watchEffect赋值的过程中出现 无法理解的错误
Vue version
3.3.4
Link to minimal reproduction
Steps to reproduce
在demo中无法复现这一问题 代码和我本地相同 环境vite 4.3.9 @vitejs/plugin-vue-jsx
在我本地中如果这里使用解构亦或者是lodash deepClone方法后 只要触发热更新就无限执行setup的所有逻辑 如果直接使用当前变量 是正常的
What is expected?
请帮我排查一下 是否是我的写法有问题 还是某些隐藏的bug
What is actually happening?
只要修改案例中 test1Child.vue 的组件 就会触发无限循环整个setup 这是我本地的报错
System Info
System:
OS: Windows 10 10.0.22621
CPU: (16) x64 11th Gen Intel(R) Core(TM) i9-11900K @ 3.50GHz
Memory: 11.60 GB / 31.32 GB
Binaries:
Node: 18.16.0 - C:\Program Files\nodejs\node.EXE
npm: 9.5.1 - C:\Program Files\nodejs\npm.CMD
pnpm: 8.5.1 - C:\Program Files\nodejs\pnpm.CMD
Browsers:
Edge: Spartan (44.22621.1992.0), Chromium (115.0.1901.183)
Internet Explorer: 11.0.22621.1
Any additional comments?
No response
@jc840031139
Reason
首先watchEffect
会立即执行回调,执行时机是Vue
组件更新之前被调用,在子组件test1Child
修改testValRef
的值,testValRef
发生改变,会立即触发父组件test1
组件的更新(在父组件test1
中使用了testValRef
,所以会更新),父组件test1
组件更新时先执行setup
,执行setup
时,watchEffect
会立即执行回调,触发父组件test1
组件的更新,此时updateQueue
一直在增加,并没有真正执行组件渲染,来消费update
。此时updateQueue
增加至最大值RECURSION_LIMIT
,抛出警告,这个值是100
,可以在源码中搜索。
可以在项目中Vue
源码这里打上断点看一下,queue
会一直增加,而flushJobs
没有机会执行。
function queueJob(job) {
debugger
if (!queue.length || !queue.includes(
job,
isFlushing && job.allowRecurse ? flushIndex + 1 : flushIndex
)) {
if (job.id == null) {
queue.push(job);
} else {
queue.splice(findInsertionIndex(job.id), 0, job);
}
queueFlush();
}
}
function queueFlush() {
debugger
if (!isFlushing && !isFlushPending) {
isFlushPending = true;
currentFlushPromise = resolvedPromise.then(flushJobs);
}
}
而使用watchPostEffect
,则是在组件渲染之后再执行,此时flushJobs
已经执行完成,此时在子组件test1Child
修改testValRef
的值,触发test1
的更新,flushJobs
会正常执行。
这个异常的重点是,updateQueue
还未执行完毕,就又来了新的update
进入queue
,最后会超过最大的update
限制 ,然后抛出警告。
Docs
https://cn.vuejs.org/guide/essentials/watchers.html#callback-flush-timing https://cn.vuejs.org/api/reactivity-core.html#watcheffect
Code
检查是否超出RECURSION_LIMIT
并抛出警告的部分
function checkRecursiveUpdates(seen, fn) {
if (!seen.has(fn)) {
seen.set(fn, 1);
} else {
const count = seen.get(fn);
if (count > RECURSION_LIMIT) {
const instance = fn.ownerInstance;
const componentName = instance && getComponentName(instance.type);
warn(
`Maximum recursive updates exceeded${componentName ? ` in component <${componentName}>` : ``}. This means you have a reactive effect that is mutating its own dependencies and thus recursively triggering itself. Possible sources include component template, render function, updated hook or watcher source function.`
);
return true;
} else {
seen.set(fn, count + 1);
}
}
}