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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.01 16:54:53