基于本文回答

播面 播面

刷题像听歌,多听自然懂
0
评论

Next.js的Intercepting Routes(拦截路由)

知识点图片

Next.js 的 Intercepting Routes(拦截路由) 是 App Router 中一个非常强大且独特的功能。

简单来说,它允许你在当前布局(Layout)中加载另一个路由,就像是把那个页面“拦截”并嵌入到了当前页面中,而不是完全跳转过去。

核心概念:软导航 vs 硬导航

拦截路由最典型的行为差异在于用户是如何访问该 URL 的:

  1. 软导航(点击 Link 跳转): 当你通过 <Link /> 点击进入该路由时,Next.js 会拦截这个请求,并在当前页面保留上下文的同时,展示目标页面的内容(通常以模态框 Modal 的形式)。
  2. 硬导航(刷新或直接访问 URL): 当你刷新页面、或者直接把 URL 发给朋友打开时,拦截不会发生。Next.js 会渲染该路由原本的独立页面

经典使用场景

  • Instagram/Pinterest 图片流:
    • 你在浏览图片列表 (/feed)。
    • 点击一张图片,URL 变为 /photo/123,但背景依然是列表,图片以弹窗形式浮在上面。
    • 如果你刷新这个 /photo/123 页面,或者把链接发给别人,打开后看到的是一张完整的独立图片详情页,没有背景列表。
  • 登录弹窗:
    • 在任何页面点击“登录”,URL 变为 /login,弹出登录框,背景依然是刚才浏览的页面。
    • 直接访问 /login 则显示全屏登录页。

路由匹配约定

拦截路由使用特殊的文件夹命名语法,类似于文件系统的相对路径:

  • (.):匹配同一层级的路由。
  • (..):匹配上一层级的路由。
  • (..)(..):匹配上两层级的路由。
  • (...):匹配根目录app 目录)下的路由。

实战示例:创建一个“图片详情弹窗”

为了实现“点击图片出弹窗,刷新变独立页面”的效果,我们需要结合 Parallel Routes(并行路由)Intercepting Routes(拦截路由)

假设我们要实现:

  1. 主页 / 展示图片列表。
  2. 点击图片跳转 /photo/[id]
  3. 在主页点击时,/photo/[id] 以 Modal 显示;直接访问时显示独立页面。

1. 目录结构

plaintext
app/
├── layout.tsx          # 根布局
├── page.tsx            # 主页 (显示图片列表)
├── @modal/             # 并行路由 Slot (用于放模态框)
│   ├── default.tsx     # 默认处理 (返回 null)
│   └── (.)photo/[id]/  # <--- 拦截路由!拦截同一层级的 photo/[id]
│       └── page.tsx    # 拦截后显示的 UI (Modal 组件)
└── photo/[id]/         # 常规路由
    └── page.tsx        # 独立访问时显示的 UI (全屏详情页)

2. 代码实现

A. 根布局 (app/layout.tsx)
我们需要定义 @modal 插槽来容纳拦截后的内容。

tsx
export default function Layout({
  children,
  modal
}: {
  children: React.ReactNode;
  modal: React.ReactNode;
}) {
  return (
    <html>
      <body>
        {children}
        {modal}
      </body>
    </html>
  );
}

B. 主页 (app/page.tsx)
包含指向 /photo/1 的链接。

tsx
import Link from 'next/link';

export default function Page() {
  return (
    <div>
      <h1>图片列表</h1>
      <Link href="/photo/1">
        <img src="/img1.jpg" alt="Photo 1" width={200} />
      </Link>
    </div>
  );
}

C. 拦截路由页面 (app/@modal/(.)photo/[id]/page.tsx)
这是软导航时看到的内容(即模态框)。注意文件夹名 (.)photo 表示拦截同级的 photo 路由。

tsx
import { Modal } from '@/components/Modal'; // 假设你有一个 Modal 组件

export default function PhotoModal({ params }: { params: { id: string } }) {
  return (
    <Modal>
      <h1>这是拦截后的弹窗视图 (ID: {params.id})</h1>
      <p>背景依然是主页</p>
    </Modal>
  );
}

D. 常规路由页面 (app/photo/[id]/page.tsx)
这是硬导航(刷新/直接访问)时看到的内容。

tsx
export default function PhotoPage({ params }: { params: { id: string } }) {
  return (
    <div className="full-page-container">
      <h1>这是独立的详情页面 (ID: {params.id})</h1>
      <p>没有背景列表,是一个完整的页面。</p>
    </div>
  );
}

E. 默认处理 (app/@modal/default.tsx)
当不在拦截状态时(比如用户在主页),@modal 插槽需要渲染点什么。通常返回 null

tsx
export default function Default() {
  return null;
}

总结:为什么要用它?

  1. URL 可分享性 (Shareable URLs): 即使是弹窗,URL 也会更新。用户复制链接发给别人,别人能看到对应的内容(虽然是以独立页面的形式),解决了传统 SPA 弹窗无法分享具体内容的问题。
  2. 上下文保留 (Context Preservation): 用户不需要离开当前的列表页就能查看详情,关闭弹窗后可以继续刚才的浏览位置,体验非常流畅。
  3. 渐进式增强: 既拥有 SPA 的流畅体验,又保留了传统网页的深层链接能力。

注意事项

  • 路径层级: (..) 是基于路由片段(Route Segments)的,而不是严格的文件系统文件夹。如果你使用了 Route Groups(如 (shop)),计算层级时可能会让你感到困惑,需要仔细核对。
  • 必须配合 Link 组件: 拦截路由只有通过 Next.js 的 <Link> 组件或 router.push() 导航时才会触发。浏览器地址栏直接输入是不会触发拦截的。
00:00
00:00