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

Next.js+TypeScript栈溢出错误排查及购物车计数实现求助

解决Next.js + TypeScript中「RangeError: Maximum call stack size exceeded」错误

错误根源:组件循环引用

你的代码存在循环导入+无限渲染的问题:

  • Shop组件导入并渲染NavBar
  • NavBar导入并渲染ShoppingCart
  • ShoppingCart又导入并渲染Shop
    这形成了闭环,组件会无限递归渲染,最终触发栈溢出错误。

修复方案

1. 拆解循环依赖,调整组件层级

将购物车状态提升到页面层级管理,让组件各司其职:

  • NavBar:只负责导航栏渲染,接收外部传递的购物车状态
  • ShoppingCart:仅展示购物车图标和数量,不渲染商品列表
  • Shop:专注展示商品和添加购物车按钮,不渲染导航栏

2. 具体代码修改

修改Shop组件(原page.tsx)

移除导航栏导入和渲染,修复列表key问题:

"use client";

import React from "react";
import { BackgroundGradient } from "../components/ui/background-gradient";
import Image from "next/image";
import FetchData from "../FetchData";
import { useState } from 'react';

interface ShopProps {
  onCount: number;
  onItemPress: () => void;
}

const Shop: React.FC<ShopProps> = ({ onItemPress }) => {
    interface Product {
      id: number;
      title: string;
      description: string;
      price: number;
      image: string;
    }
    const [productData, setProductData] = useState<Product[] | null>(null);

    return (
      <>
        <FetchData onDataReceived={setProductData}>
          {null}
        </FetchData>
        
        <div className="rounded-[22px] max-w-sm pt-20">
          {productData && (
            <ul>
                {productData.map((product: Product) => (
                  // 用product.id作为唯一key,避免重复
                  <BackgroundGradient className="rounded-[22px] max-w-sm p-4 sm:p-10 bg-white dark:bg-zinc-900" key={product.id}>
                    <li key={product.id}>
                      <Image
                        src={product.image} 
                        alt={product.title}
                        width="400"
                        height="400"
                        className="object-contain"
                      />
                      <p className="text-base sm:text-xl text-black mt-4 mb-2 dark:text-neutral-200">{product.title}</p>
                      <p className="text-sm text-neutral-600 dark:text-neutral-400">{product.description}</p>
                      <div className="flex items-center space-x-2">
                        <button className="rounded-full pl-4 pr-1 py-1 text-white flex items-center space-x-1 bg-black mt-4 text-xs font-bold dark:bg-zinc-800">
                          <span>Buy now </span>
                          <span className="bg-zinc-700 rounded-full text-[0.6rem] px-2 py-0 text-white">
                          ${product.price}
                          </span>
                        </button>
                        <button
                          onClick={onItemPress}
                          className="rounded-full pl-4 pr-4 py-1 text-white flex items-center 
                                    justify-center space-x-1 bg-black mt-4 text-xs font-bold dark:bg-zinc-800"
                        >
                          Add to Cart
                        </button>
                      </div>
                    </li>
                  </BackgroundGradient>
                ))}
            </ul>
          )}
          <BackgroundGradient className="rounded-[22px] max-w-sm p-4 sm:p-10 bg-white dark:bg-zinc-900">
            <Image
              src='/og-package-react'
              alt="jordans"
              height="400"
              width="400"
              className="object-contain"
            />
            <p className="text-base sm:text-xl text-black mt-4 mb-2 dark:text-neutral-200">
              Air Jordan 4 Retro Reimagined
            </p>
     
            <p className="text-sm text-neutral-600 dark:text-neutral-400">
              The Air Jordan 4 Retro Reimagined Bred will release on Saturday,
              February 17, 2024. Your best opportunity to get these right now is by
              entering raffles and waiting for the official releases.
            </p>
            <button className="rounded-full pl-4 pr-1 py-1 text-white flex items-center space-x-1 bg-black mt-4 text-xs font-bold dark:bg-zinc-800">
              <span>Buy now </span>
              <span className="bg-zinc-700 rounded-full text-[0.6rem] px-2 py-0 text-white">
                $100
              </span>
            </button>
          </BackgroundGradient>
        </div>
      </>
      );
}

export default Shop;

修改ShoppingCart组件

简化为仅展示购物车图标,状态由父组件传递:

"use client";
import React from "react";
import Badge from "@material-ui/core/Badge";
import ShoppingCartIcon from "@material-ui/icons/ShoppingCart";

interface ShoppingCartProps {
  itemCount: number;
}

export default function ShoppingCart({ itemCount }: ShoppingCartProps) {
    return (
        <div>
            <Badge color="secondary" badgeContent={itemCount} overlap="rectangular">
                <ShoppingCartIcon />
            </Badge>
        </div>
    );
}

修改NavBar组件

接收购物车状态,传递给ShoppingCart:

"use client";
import React from "react";
import { HoveredLink, Menu, MenuItem, ProductItem } from "./ui/navbar-menu";
import { cn } from "@/app/utils/cn";
import ShoppingCart from "./ShoppingCart";
import { useState } from 'react';

interface NavbarProps {
  className?: string;
  cartCount: number;
}

function Navbar({ className, cartCount }: NavbarProps) {
  const [activeItem, setActiveItem] = useState<string | null>(null);
  return (
    <div className={cn("fixed top-10 inset-x-0 max-w-2xl mx-auto z-50", className)}>
      <Menu setActive={setActiveItem}>
        <a className="text-white" href="/">Home</a>
        <a className="text-white" href="/shop">Shop</a>
      </Menu>
      <ShoppingCart itemCount={cartCount} />
    </div>
  );
}

interface NavbarDemoProps {
  cartCount: number;
}

export default function NavbarDemo({ cartCount }: NavbarDemoProps) {
  return (
    <div className="relative w-full flex items-center justify-center">
      <Navbar className="top-2" cartCount={cartCount} />
    </div>
  );
}

创建页面入口(app/shop/page.tsx)

在页面层级统一管理购物车状态,渲染导航栏和商品列表:

"use client";
import React, { useState } from "react";
import NavbarDemo from "@/components/NavBar";
import Shop from "./Shop";

export default function ShopPage() {
  const [cartCount, setCartCount] = useState(0);

  const handleAddItem = () => {
    setCartCount(prev => prev + 1);
  };

  return (
    <div>
      <NavbarDemo cartCount={cartCount} />
      <Shop onItemPress={handleAddItem} />
    </div>
  );
}

3. 额外优化点

  • FetchData组件的useEffect依赖onDataReceived,每次setProductData会生成新函数,导致重复请求。可以用useCallback包裹setProductData,或者修改依赖为[](仅初始化请求一次)。

内容的提问来源于stack exchange,提问作者hellokev

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.23 11:07:03