You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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的情况。
修复方案

按以下步骤修改即可解决问题:

  1. 切断递归触发链路
    检查wallet store里的逻辑,删除所有监听unstakedNFTs/stakedNFTs变化就自动调用fetchNFTs/fetchUnstakedNFTs的watcher,不要在组件updated钩子、模板插值里直接调用fetch方法。fetch逻辑仅保留三个触发时机:钱包连接成功时、用户手动触发刷新时、质押/解质押交易确认回调中,删除onStake方法末尾多余的await wallet.fetchNFTs()调用。
    同时给fetch函数加并发锁,防止上一次拉取未完成时重复调用:
    // 在store里新增一个锁状态
    const fetchingUnstaked = ref(false)
    
  2. 重构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
      }
    }
    
  3. 修复组件里的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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.31 22:51:07