Next.js手把手系列:04、路由篇 | 动态路由、路由组、平行路由和拦截路由

  • Louis
  • 更新于 2024-07-29 09:09
  • 阅读 389

实际项目开发的时候,有的路由场景会比较复杂,比如数据库里的文章有很多,我们不可能一一去定义路由,此时该怎么办?组织代码的时候,有的路由是用于移动端,有的路由是用于PC端,该如何组织代码?

前言

实际项目开发的时候,有的路由场景会比较复杂,比如数据库里的文章有很多,我们不可能一一去定义路由,此时该怎么办?组织代码的时候,有的路由是用于移动端,有的路由是用于 PC 端,该如何组织代码?如何有条件的渲染页面,比如未授权的时候显示登录页?如何让同一个路由根据不同的场景展示不同的内容?

本篇我们会一一解决这些问题,在此篇,你将会感受到 App Router 强大的路由功能。

1. 动态路由(Dynamic Routes)

有的时候,你并不能提前知道路由的地址,就比如根据 URL 中的 id 参数展示该 id 对应的文章内容,文章那么多,我们不可能一一定义路由,这个时候就需要用到动态路由。

1.1. [folderName]

使用动态路由,你需要将文件夹的名字用方括号括住,比如 [id][slug]。这个路由的名字会作为 params prop 传给布局、 页面、 路由处理程序 以及 generateMetadata 函数。

举个例子,我们在 app/blog 目录下新建一个名为 [slug] 的文件夹,在该文件夹新建一个 page.js 文件,代码如下:

// app/blog/[slug]/page.js
export default function Page({ params }) {
  return <div>My Post: {params.slug}</div>
}

Xnip2024-07-14_16-31-05.png

当你访问 /blog/a的时候,params 的值为 { slug: 'a' }

当你访问 /blog/louis的时候,params 的值为 { slug: 'louis' }

以此类推。

1.2. [...folderName]

在命名文件夹的时候,如果你在方括号内添加省略号,比如 [...folderName],这表示捕获所有后面所有的路由片段。

也就是说,app/shop/[...slug]/page.js会匹配 /shop/clothes,也会匹配 /shop/clothes/tops/shop/clothes/tops/t-shirts等等。

举个例子,app/shop/[...slug]/page.js的代码如下:

// app/shop/[...slug]/page.js
export default function Page({ params }) {
  return <div>My Shop: {JSON.stringify(params)}</div>
}

效果如下:

Xnip2024-07-14_16-36-27.png

当你访问 /shop/a的时候,params 的值为 { slug: ['a'] }

当你访问 /shop/a/b的时候,params 的值为 { slug: ['a', 'b'] }

当你访问 /shop/a/b/c的时候,params 的值为 { slug: ['a', 'b', 'c'] }

以此类推。

1.3. [[...folderName]]

在命名文件夹的时候,如果你在双方括号内添加省略号,比如 [[...folderName]],这表示可选的捕获所有后面所有的路由片段。

也就是说,app/shop/[[...slug]]/page.js会匹配 /shop,也会匹配 /shop/clothes、 /shop/clothes/tops/shop/clothes/tops/t-shirts等等。

它与上一种的区别就在于,不带参数的路由也会被匹配(就比如 /shop

举个例子,app/shop/[[...slug]]/page.js的代码如下:

// app/shop/[[...slug]]/page.js
export default function Page({ params }) {
  return <div>My Shop: {JSON.stringify(params)}</div>
}

Xnip2024-07-14_16-41-01.png

当你访问 /shop的时候,params 的值为 {}

当你访问 /shop/a的时候,params 的值为 { slug: ['a'] }

当你访问 /shop/a/b的时候,params 的值为 { slug: ['a', 'b'] }

当你访问 /shop/a/b/c的时候,params 的值为 { slug: ['a', 'b', 'c'] }

以此类推。

2. 路由组(Route groups)

在 app目录下,文件夹名称通常会被映射到 URL 中,但你可以将文件夹标记为路由组,阻止文件夹名称被映射到 URL 中。

使用路由组,你可以将路由和项目文件按照逻辑进行分组,但不会影响 URL 路径结构。路由组可用于比如:

  1. 按站点、意图、团队等将路由分组
  2. 在同一层级中创建多个布局,甚至是创建多个根布局

那么该如何标记呢?把文件夹用括号括住就可以了,就比如 (dashboard)

举些例子:

2.1. 按逻辑分组

将路由按逻辑分组,但不影响 URL 路径:

Xnip2024-07-14_16-45-23.png

你会发现,最终的 URL 中省略了带括号的文件夹(上图中的(marketing)(shop))。

2.2. 创建不同布局

借助路由组,即便在同一层级,也可以创建不同的布局:

Xnip2024-07-15_09-24-02.png

在这个例子中,/account 、/cart/checkout 都在同一层级。但是 /account和 /cart使用的是 /app/(shop)/layout.js布局和app/layout.js布局,/checkout使用的是 app/layout.js

2.3. 创建多个根布局

创建多个根布局:

Xnip2024-07-15_09-24-56.png

创建多个根布局,你需要删除掉 app/layout.js 文件,然后在每组都创建一个 layout.js文件。创建的时候要注意,因为是根布局,所以要有 <html> 和 <body> 标签。

这个功能很实用,比如你将前台购买页面和后台管理页面都放在一个项目里,一个 C 端,一个 B 端,两个项目的布局肯定不一样,借助路由组,就可以轻松实现区分。

再多说几点:

  1. 路由组的命名除了用于组织之外并无特殊意义。它们不会影响 URL 路径。
  2. 注意不要解析为相同的 URL 路径。举个例子,因为路由组不影响 URL 路径,所以 (marketing)/about/page.js和 (shop)/about/page.js都会解析为 /about,这会导致报错。
  3. 创建多个根布局的时候,因为删除了顶层的 app/layout.js文件,访问 /会报错,所以app/page.js需要定义在其中一个路由组中。
  4. 跨根布局导航会导致页面完全重新加载,就比如使用 app/(shop)/layout.js根布局的 /cart 跳转到使用 app/(marketing)/layout.js根布局的 /blog 会导致页面重新加载(full page load)。

注:当定义多个根布局的时候,使用 app/not-found.js会出现问题。具体参考 《Next.js v14 如何为多个根布局自定义不同的 404 页面?竟然还有些麻烦》

3. 平行路由(Parallel Routes)

平行路由可以使你在同一个布局中同时或者有条件的渲染一个或者多个页面(类似于 Vue 的插槽功能)。

3.1. 用途 1:条件渲染

举个例子,在后台管理页面,需要同时展示团队(team)和数据分析(analytics)页面:

Xnip2024-07-15_14-08-24.png

平行路由的使用方式是将文件夹以 @作为开头进行命名,比如在上图中就定义了两个插槽 @team 和 @analytics

插槽会作为 props 传给共享的父布局。在上图中,app/layout.js 从 props 中获取了 @team 和 @analytics 两个插槽的内容,并将其与 children 并行渲染:

// app/layout.js
// 这里我们用了 ES6 的解构,写法更简洁一点
export default function Layout({ children, team, analytics }) {
  return (
    <>
      {children}
      {team}
      {analytics}
    </>
  )
}

注:从这张图也可以看出,children prop 其实就是一个隐式的插槽,/app/page.js相当于 app/@children/page.js

除了让它们同时展示,你也可以根据条件判断展示:

Xnip2024-07-15_14-10-23.png

在这个例子中,先在布局中获取用户的登录状态,如果登录,显示 dashboard 页面,没有登录,显示 login 页面。这样做的一大好处就在于代码完全分离。

3.2. 用途 2:独立路由处理

平行路由可以让你为每个路由定义独立的错误处理和加载界面:

Xnip2024-07-15_14-11-55.png

3.3. 用途 3:子导航

注意我们描述 team 和 analytics 时依然用的是“页面”这个说法,因为它们就像书写正常的页面一样使用 page.js。除此之外,它们也能像正常的页面一样,添加子页面,比如我们在 @analytics 下添加两个子页面:/page-views and /visitors

Xnip2024-07-15_14-13-09.png

平行路由跟路由组一样,不会影响 URL,所以 /@analytics/page-views/page.js 对应的地址是 /page-views/@analytics/visitors/page.js 对应的地址是 /visitors,你可以导航至这些路由:


// app/layout.js
import Link from "next/link";

export default function RootLayout({ children, analytics }) {
  return (
    <html>
      <body>
        <nav>
          <Link href="/">Home</Link>
          <br />
          <Link href="/page-views">Page Views</Link>
          <br />
          <Link href="/visitors">Visitors</Link>
        </nav>
        <h1>root layout</h1>
        {analytics}
        {children}
      </bod...

剩余50%的内容订阅专栏后可查看

点赞 0
收藏 0
分享
本文参与登链社区写作激励计划 ,好文好收益,欢迎正在阅读的你也加入。

0 条评论

请先 登录 后评论
Louis
Louis
0x2b75...1A7D
区块链开发工程师,技术交流或者有工作机会可加VX: magicalLouis