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

Next.js 13(App目录)如何实现基于动态路由的模态框?

Next.js 13(App目录)实现动态路由模态框方案

完全可以实现你要的交互效果,以下是具体的实现步骤和代码调整方案:

核心需求回顾

  • 列表页点击卡片:弹出模态框,同时URL更新为/users/[userId],无浏览器刷新
  • 直接访问/users/[userId]:显示用户完整详情页

代码调整方案

1. 完善模态框状态上下文(ProfileModalProvider)

在上下文中管理模态框的显示状态,同时监听路由变化同步状态:

// app/providers/ProfileModalProvider.tsx
'use client';

import { createContext, useContext, useEffect, useState } from 'react';
import { usePathname, useRouter } from 'next/navigation';

interface ProfileModalContextType {
  isModalOpen: boolean;
  currentUserId: string | null;
  openModal: (userId: string) => void;
  closeModal: () => void;
}

const ProfileModalContext = createContext<ProfileModalContextType | undefined>(undefined);

export function ProfileModalProvider({ children }: { children: React.ReactNode }) {
  const [isModalOpen, setIsModalOpen] = useState(false);
  const [currentUserId, setCurrentUserId] = useState<string | null>(null);
  const pathname = usePathname();
  const router = useRouter();

  // 监听路由变化,判断是否需要打开模态框
  useEffect(() => {
    const userIdMatch = pathname.match(/^\/users\/([^/]+)$/);
    if (userIdMatch) {
      setCurrentUserId(userIdMatch[1]);
      setIsModalOpen(true);
    } else {
      setIsModalOpen(false);
      setCurrentUserId(null);
    }
  }, [pathname]);

  const openModal = (userId: string) => {
    setCurrentUserId(userId);
    setIsModalOpen(true);
    router.push(`/users/${userId}`);
  };

  const closeModal = () => {
    setIsModalOpen(false);
    setCurrentUserId(null);
    router.push('/'); // 关闭时返回首页
  };

  return (
    <ProfileModalContext.Provider value={{ isModalOpen, currentUserId, openModal, closeModal }}>
      {children}
    </ProfileModalContext.Provider>
  );
}

export const useProfileModal = () => {
  const context = useContext(ProfileModalContext);
  if (!context) {
    throw new Error('useProfileModal must be used within a ProfileModalProvider');
  }
  return context;
};

2. 调整根布局(layout.tsx)

保持原有结构,确保上下文包裹所有子组件:

// app/layout.tsx
import { ProfileModalProvider } from './providers/ProfileModalProvider';

export default async function RootLayout({ children }: { children: React.ReactNode }) {
  return (
    <html lang="zh-CN">
      <head />
      <body>
        <ProfileModalProvider>
          {children}
        </ProfileModalProvider>
      </body>
    </html>
  );
}

3. 修改首页(page.tsx)

通过上下文控制模态框显示,移除直接导入的模态框:

// app/page.tsx
'use client';

import UserList from './components/UserList';
import Hero from './components/Hero';
import SearchSection from './components/SearchSection';
import ProfileModal from './components/ProfileModal';
import { useProfileModal } from './providers/ProfileModalProvider';
import getUsers from './lib/getUsers'; // 假设你的用户数据获取逻辑在这里

export default function Home() {
  const users = getUsers(); // 替换为你的实际数据获取逻辑
  const { isModalOpen } = useProfileModal();

  return (
    <main>
      <Hero />
      <SearchSection />
      <UserList users={users} />
      {isModalOpen && <ProfileModal />}
    </main>
  );
}

4. 调整用户卡片按钮(ViewProfileButton.tsx)

调用上下文的openModal方法替代Link跳转:

// app/components/ViewProfileButton.tsx
'use client';

import { useProfileModal } from '../providers/ProfileModalProvider';

interface Props {
  user: { id: string; /* 其他用户属性 */ };
}

export default function ViewProfileButton({ user }: Props) {
  const { openModal } = useProfileModal();

  return (
    <div onClick={() => openModal(user.id)} style={{ cursor: 'pointer' }}>
      View User
    </div>
  );
}

5. 实现动态路由页面(/users/[userId]/page.tsx)

判断路由访问场景,决定是否渲染完整页面:

// app/users/[userId]/page.tsx
'use client';

import { useProfileModal } from '../../providers/ProfileModalProvider';
import { useParams } from 'next/navigation';

export default function UserPage() {
  const params = useParams();
  const userId = params.userId as string;
  const { isModalOpen } = useProfileModal();

  // 如果是从首页点击打开的模态框,不渲染完整页面
  if (isModalOpen) {
    return null;
  }

  // 直接访问路由时,渲染完整用户详情页
  return (
    <div className="user-full-page">
      <h1>用户详情页</h1>
      <p>用户ID: {userId}</p>
      {/* 其他用户详情内容 */}
    </div>
  );
}

6. 实现模态框组件(ProfileModal.tsx)

加载用户数据并提供关闭功能:

// app/components/ProfileModal.tsx
'use client';

import { useProfileModal } from '../providers/ProfileModalProvider';
import { useEffect, useState } from 'react';

interface User {
  id: string;
  name: string;
  // 其他用户属性
}

export default function ProfileModal() {
  const { currentUserId, closeModal } = useProfileModal();
  const [user, setUser] = useState<User | null>(null);

  useEffect(() => {
    if (!currentUserId) return;

    // 替换为你的用户详情API请求
    const fetchUser = async () => {
      const res = await fetch(`/api/users/${currentUserId}`);
      const data = await res.json();
      setUser(data);
    };

    fetchUser();
  }, [currentUserId]);

  if (!user) return null;

  return (
    <div className="modal-overlay" onClick={closeModal}>
      <div className="modal-content" onClick={(e) => e.stopPropagation()}>
        <button onClick={closeModal}>关闭</button>
        <h2>{user.name}</h2>
        {/* 模态框内的用户详情内容 */}
      </div>
    </div>
  );
}

关键逻辑说明

  • 通过上下文统一管理模态框状态,避免状态分散
  • 监听路由变化,自动同步模态框的显示/隐藏状态
  • 动态路由页面通过判断模态框状态,决定是否渲染完整页面
  • 按钮点击时通过上下文方法更新路由和模态框状态,替代传统的Link跳转

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.09 20:01:03