Next.js+TypeScript栈溢出错误排查及购物车计数实现求助
解决Next.js + TypeScript中「RangeError: Maximum call stack size exceeded」错误
错误根源:组件循环引用
你的代码存在循环导入+无限渲染的问题:
Shop组件导入并渲染NavBarNavBar导入并渲染ShoppingCartShoppingCart又导入并渲染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
相关产品推荐
相关产品推荐

