今天早上给自己的组件库封装了一个 space 组件,测试的时候没察觉到,等到下午写其他组件的时候,发现浏览器在每次数据响应更新时都会发出警告:[Vue warn]: Maximum recursive updates exceeded in component <pdSpace>. 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.
其中的大体意思是:组件中超过了最大递归更新。这意味着您有一个反应性效果,它正在改变自己的依赖关系,从而递归地触发自己。可能的源包括组件模板、渲染函数、更新的钩子或观察者源函数。 我在网上查了一下,不少人说出现这种警告的原因是因为组件内部的逻辑判断没做好,出现了死循环才导致了内存泄露发出了警告。
解决方案
先说结果,类似于 [Vue warn]: Maximum recursive updates exceeded in component这类警告问题,通常是因为循环依赖或死循环导致栈溢出,可以考虑以下步骤来排查问题:
- 检查代码中的逻辑判断部分,确保没有出现循环依赖或死循环的情况。
- 检查组件的模板和计算属性,避免在计算属性中引用自身。
- 检查组件的响应式数据,确保没有循环引用或触发了无限的数据更新。
具体实践
后面排查了一下组件当中我只有这一处的代码用forEach做了循环:
// 获取默认插槽内容
const slots = useSlots();
const slotList: Ref<VNode[]> = ref([]);
......
// 循环遍历,使用 h 函数给插槽元素每个元素添加一层 div,
slots.default?.().forEach((item: VNode) => {
slotList.value.push(
h(
"div",
{
className: "pd-space-item",
style: "width:fit-content",
},
item
)
);
});
但是这也看不出哪里出现了死循环,后面问了下 AI 才发现问题所在:因为我是在setup函数中使用h函数来创建div元素,然后将其push到slotList数组当中。这咋一看虽然没什么问题,但是当触发视图更新时,组件会重新进行渲染,在重新渲染的时候,由于slotList数组发生了变化,会再次进入到setup函数从而导致循环递归最终浏览器出现警告。
所以本质上还是因为循环递归导致的,所以就从循环部分入手:既然原来的代码会重复循环,那就避免它发生,将原来的slotList数组定义成一个响应式对象来避免触发组件重新渲染,具体可以通过computed计算属性实现:
// 使用 map 将每个遍历到的元素映射为一个新的值,并使用 h 函数来创建新的 div 元素作为 slotList 数组的元素
const slotList = computed(() => {
return slots.default?.().map((item: VNode) => {
return h('div', {
className: 'pd-space-item',
style: 'width: fit-content',
}, [item]);
}) || [];
});
这样每次视图更新时就不会出现相关的问题警告了。