Next.js最新App Router组件无限重渲染问题求助
问题:Next.js App Router 组件初始无限重渲染,不想使用 Suspense
使用最新版 Next.js App Router,组件加载数据时出现初始无限重渲染,数据本身是有效的。添加 React Suspense 可以解决该问题,但不想使用——因为重渲染时会显示 fallback 组件,希望实现正常重渲染而非无限循环。
相关代码
SidebarContent.tsx
"use client"; import { useEffect, useState } from "react"; import SlidebarItems from "./SlidebarItems"; export default function SlidebarContent() { const [content, setContent] = useState<any>([]); useEffect(() => { async function getData() { const res = await fetch("/api"); const data = await res.json(); setContent(data); } getData(); }, []); const selectItem = (id: string) => { const newContent = content.map((item) => { if (item.id === id) { return { ...item, show: true }; } else { return { ...item, show: false }; } }); setContent(newContent); }; console.log("renders content"); return ( <div className="flex justify-between items-center h-full w-full"> <div className="sliderRef flex gap-2 items-center h-full w-[85%] overflow-x-hidden"> hi {content && content.map((item) => ( <SlidebarItems key={item.id} content={item} onClick={(id) => selectItem(id)} /> ))} </div> </div> ); }
SidebarItems.js
"use client"; import Image from "next/image"; const SlidebarItems = async ({ content, onClick }) => { return ( <div className={`slidebar-content ${content.show ? "selected" : ""}`} onClick={() => onClick(content.id)} > <div className="flex flex-col items-center w-[98px] "> <Image src={content.src} alt={content.title} width={24} height={24} priority className="h-7 w-7 mb-2 " /> <h3 className="text-xs font-medium">{content.title}</h3> </div> </div> ); }; export default SlidebarItems;
解决方案
核心原因
SlidebarItems被错误声明为async组件,但内部没有任何异步逻辑(比如await操作)。在Next.js App Router的客户端组件中,async组件会强制触发React Suspense机制,即使未显式包裹Suspense,这会导致组件反复进入“加载-渲染”循环,引发无限重渲染。
修复步骤
- 移除
SlidebarItems的async关键字,因为它不需要执行异步操作:
"use client"; import Image from "next/image"; const SlidebarItems = ({ content, onClick }) => { return ( <div className={`slidebar-content ${content.show ? "selected" : ""}`} onClick={() => onClick(content.id)} > <div className="flex flex-col items-center w-[98px] "> <Image src={content.src} alt={content.title} width={24} height={24} priority className="h-7 w-7 mb-2 " /> <h3 className="text-xs font-medium">{content.title}</h3> </div> </div> ); }; export default SlidebarItems;
- 确认状态逻辑无异常:当前
SidebarContent中的useEffect依赖为空数组,只会执行一次数据获取;selectItem通过map生成新数组更新状态的逻辑也正确,不会触发意外的状态更新。
额外优化建议
- 给
content定义明确的TypeScript类型,替换any,提升代码可维护性:
interface SidebarItem { id: string; show: boolean; src: string; title: string; } // 初始化状态时指定类型 const [content, setContent] = useState<SidebarItem[]>([]);
- 在数据请求中添加错误处理,避免请求失败导致的状态异常:
useEffect(() => { async function getData() { try { const res = await fetch("/api"); if (!res.ok) throw new Error("数据请求失败"); const data = await res.json(); setContent(data); } catch (err) { console.error("获取侧边栏数据出错:", err); } } getData(); }, []);
内容的提问来源于stack exchange,提问作者Bidhanz
相关产品推荐
相关产品推荐

