Next.js 14 App Router动态路由切换时useEffect不触发问题
问题描述
我正在用Next.js 14的App Router开发应用,创建了动态路由/products/[id],并通过将params.id作为依赖项的useEffect在用户切换产品时获取数据。
问题:当使用<Link>从"/products/1"导航到"/products/2"时,useEffect未重新执行,组件没有渲染新产品数据,页面仍显示旧产品。
代码示例
app/products/[id]/page.tsx
'use client'; import { useEffect, useState } from 'react'; import { useParams } from 'next/navigation'; interface Product { id: number; title: string; description: string; } export default function ProductPage() { const params = useParams(); const id = params.id as string; const [product, setProduct] = useState<Product | null>(null); useEffect(() => { const fetchProduct = async () => { const res = await fetch(`https://dummyjson.com/products/${id}`); const data = await res.json(); setProduct(data); }; fetchProduct(); }, [id]); // id已加入依赖数组 if (!product) return <p>Loading...</p>; return ( <div> <h1>{product.title}</h1> <p>{product.description}</p> </div> ); }
导航链接(其他组件中)
<Link href="/products/2">Go to Product 2</Link>
已尝试的解决方法
- 将
id添加到useEffect依赖数组——仍未重新执行。 - 尝试用
router.push('/products/2')替代<Link>——结果相同。 - 在父布局组件上添加
key={id}强制重挂载,但想了解useEffect自身不触发的原因。 - 日志确认
params.id已更新,但useEffect仍未触发。
预期与实际行为
- 预期行为:每当
id变化时,useEffect应重新执行并获取新产品数据。 - 实际行为:
useEffect仅在初始挂载时执行,导航后仍显示旧数据。
环境
- Next.js: 14.2.x
- React: 18.x
- TypeScript: 5.x
- 使用App Router(非Pages Router)
这是Next.js 14 App Router与useEffect的已知问题吗?如何在动态路由参数变化时成功获取数据?
解决方案
这不是Next.js的已知问题,核心原因是App Router中useParams()返回的params是代理对象,引用不稳定——路由变化时params本身会生成新引用,但解构的id可能因闭包陷阱被useEffect捕获旧值,导致依赖判断失效。
可行解决方法:
方法1:直接监听params对象避免闭包问题
修改useEffect依赖为params,并在内部实时获取id:
useEffect(() => { const currentId = params.id as string; const fetchProduct = async () => { const res = await fetch(`https://dummyjson.com/products/${currentId}`); const data = await res.json(); setProduct(data); }; fetchProduct(); }, [params]); // 依赖改为params对象
方法2:结合usePathname强化路由变化感知
同时监听params和pathname,确保路由变化被精准捕获:
import { useParams, usePathname } from 'next/navigation'; // ... const pathname = usePathname(); useEffect(() => { const currentId = params.id as string; const fetchProduct = async () => { const res = await fetch(`https://dummyjson.com/products/${currentId}`); const data = await res.json(); setProduct(data); }; fetchProduct(); }, [params, pathname]); // 同时监听params和pathname
方法3:改用服务器组件(推荐,符合App Router设计)
去掉客户端组件标识,直接在服务器组件中异步获取数据,Next.js会自动在路由变化时重新执行数据逻辑:
import { notFound } from 'next/navigation'; interface Product { id: number; title: string; description: string; } export default async function ProductPage({ params }: { params: { id: string } }) { const res = await fetch(`https://dummyjson.com/products/${params.id}`, { next: { revalidate: 60 }, // 可选:设置缓存策略 }); if (!res.ok) { notFound(); } const product: Product = await res.json(); return ( <div> <h1>{product.title}</h1> <p>{product.description}</p> </div> ); }
原代码失效的核心原因
App Router中客户端组件的useParams返回代理对象,解构出的id会被闭包捕获旧值,导致useEffect的依赖对比认为值未变化,因此不触发执行。直接监听params对象能确保感知到路由变化时的引用更新。
内容的提问来源于stack exchange,提问作者Sumit Mayani
相关产品推荐
相关产品推荐

