Vue3报Maximum recursive updates exceeded递归更新超限错误
问题原因
Maximum recursive updates exceeded报错的本质是响应式状态更新触发了副作用,副作用执行又修改了同一个状态,形成无限更新循环,结合你的代码具体触发链路如下:
- 核心触发点:
unstakedNFTs是响应式数组,每次执行unstakedNFTs.value.push(newNFT)都会通知所有依赖该数组的组件/副作用重新执行。如果你的fetchUnstakedNFTs函数被watcher、组件渲染钩子、或者更新后的回调自动调用(比如写了监听钱包状态/ NFT列表变化就自动重新拉取NFT的逻辑),就会出现:push触发更新 → 更新触发fetch → fetch里又push → 再次触发更新的死循环。 - 放大问题的次要原因:
- 你在
fetchUnstakedNFTs里逐次push NFT,每加一个就触发一次响应式更新,而不是攒齐所有数据批量更新,大幅提升了循环触发的概率。 - 同一个函数里对同一个NFT重复调用了两次
getMetadata拉取元数据,拉长了异步执行时间,容易出现并发调用fetch导致的状态混乱。 - NFTPane组件里v-for使用
nft.image作为key,若出现图片地址重复、加载中字段为空的情况,会导致Vue diff算法判定节点变化,触发不必要的重渲染。同时shimmerArray = Array(9)生成的数组元素全是undefined,v-for时key重复也会触发额外重渲染。 onStake方法里既在交易确认回调里调用了wallet.fetchNFTs(),又在方法末尾额外调用了一次,容易出现并发执行fetch的情况。
- 你在
修复方案
按以下步骤修改即可解决问题:
- 切断递归触发链路
检查wallet store里的逻辑,删除所有监听unstakedNFTs/stakedNFTs变化就自动调用fetchNFTs/fetchUnstakedNFTs的watcher,不要在组件updated钩子、模板插值里直接调用fetch方法。fetch逻辑仅保留三个触发时机:钱包连接成功时、用户手动触发刷新时、质押/解质押交易确认回调中,删除onStake方法末尾多余的await wallet.fetchNFTs()调用。
同时给fetch函数加并发锁,防止上一次拉取未完成时重复调用:// 在store里新增一个锁状态 const fetchingUnstaked = ref(false) - 重构fetchUnstakedNFTs,批量更新状态+减少重复请求
把逐次push改成先收集所有待新增NFT、最后一次性追加到响应式数组,同时去掉重复的元数据请求,修复后代码如下:const fetchUnstakedNFTs = async (): Promise<void> => { if (!wallet.value?.connected || !publicKey.value) return // 加锁防止并发调用 if (fetchingUnstaked.value) return fetchingUnstaked.value = true try { const tokenAccountsFromWallet = await connection.getParsedTokenAccountsByOwner(publicKey.value, { programId: new PublicKey("TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA"), }) const newNFTsToAdd = [] const validAccountKeys = new Set() // 单次循环完成筛选+元数据拉取,避免重复请求 for (const account of tokenAccountsFromWallet.value) { const mint = account.account.data.parsed.info.mint const tokenAccountStr = account.pubkey.toString() const amount = account.account.data.parsed.info.tokenAmount.amount // 记录当前钱包内有效NFT的唯一标识 validAccountKeys.add(`${mint}-${tokenAccountStr}`) // 持仓量不是1直接跳过 if (amount !== "1") continue // 已经存在于列表里的NFT跳过,不用重复拉取元数据 const exists = unstakedNFTs.value.find(nft => nft.mintId === mint && nft.tokenAccount === tokenAccountStr) if (exists) continue const metadata = await getMetadata(mint) if (!metadata?.creators?.[0] || metadata.creators[0].address !== nftCreator.toString()) continue const { data } = await axios.get(metadata.uri) newNFTsToAdd.push({ name: data.name, symbol: metadata.symbol, image: data.image, mintId: mint, tokenAccount: tokenAccountStr, isStaking: false, isSelected: false, }) } // 先清理已经不在钱包里的旧NFT数据 unstakedNFTs.value = unstakedNFTs.value.filter(nft => validAccountKeys.has(`${nft.mintId}-${nft.tokenAccount}`)) // 一次性批量追加新NFT,仅触发一次响应式更新 if (newNFTsToAdd.length) { unstakedNFTs.value.push(...newNFTsToAdd) } } finally { fetchingUnstaked.value = false } } - 修复组件里的v-for key问题,避免无效重渲染
把NFT列表的v-for key从nft.image换成全局唯一的nft.mintId,同时修复骨架屏数组的key重复问题:// 替换原来的const shimmerArray = Array(9) const shimmerArray = Array.from({length: 9}, (_, idx) => idx)<!-- NFT列表部分修改key --> <button :disabled="nft.isStaking" @click="selectNFT(nft)" v-for="nft in nfts" :key="nft.mintId" class="h-52 w-52 relative m-2" >
内容的提问来源于stack exchange,提问作者Yusuf-Uluc
相关产品推荐
相关产品推荐

