实际项目开发的时候,有的路由场景会比较复杂,比如数据库里的文章有很多,我们不可能一一去定义路由,此时该怎么办?组织代码的时候,有的路由是用于移动端,有的路由是用于PC端,该如何组织代码?
实际项目开发的时候,有的路由场景会比较复杂,比如数据库里的文章有很多,我们不可能一一去定义路由,此时该怎么办?组织代码的时候,有的路由是用于移动端,有的路由是用于 PC 端,该如何组织代码?如何有条件的渲染页面,比如未授权的时候显示登录页?如何让同一个路由根据不同的场景展示不同的内容?
本篇我们会一一解决这些问题,在此篇,你将会感受到 App Router 强大的路由功能。
有的时候,你并不能提前知道路由的地址,就比如根据 URL 中的 id 参数展示该 id 对应的文章内容,文章那么多,我们不可能一一定义路由,这个时候就需要用到动态路由。
使用动态路由,你需要将文件夹的名字用方括号括住,比如 [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>
}
当你访问 /blog/a
的时候,params
的值为 { slug: 'a' }
。
当你访问 /blog/louis
的时候,params
的值为 { slug: 'louis' }
。
以此类推。
在命名文件夹的时候,如果你在方括号内添加省略号,比如 [...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>
}
效果如下:
当你访问 /shop/a
的时候,params
的值为 { slug: ['a'] }
。
当你访问 /shop/a/b
的时候,params
的值为 { slug: ['a', 'b'] }
。
当你访问 /shop/a/b/c
的时候,params
的值为 { slug: ['a', 'b', 'c'] }
。
以此类推。
在命名文件夹的时候,如果你在双方括号内添加省略号,比如 [[...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>
}
当你访问 /shop
的时候,params 的值为 {}
。
当你访问 /shop/a
的时候,params 的值为 { slug: ['a'] }
。
当你访问 /shop/a/b
的时候,params 的值为 { slug: ['a', 'b'] }
。
当你访问 /shop/a/b/c
的时候,params 的值为 { slug: ['a', 'b', 'c'] }
。
以此类推。
在 app
目录下,文件夹名称通常会被映射到 URL 中,但你可以将文件夹标记为路由组,阻止文件夹名称被映射到 URL 中。
使用路由组,你可以将路由和项目文件按照逻辑进行分组,但不会影响 URL 路径结构。路由组可用于比如:
那么该如何标记呢?把文件夹用括号括住就可以了,就比如 (dashboard)
。
举些例子:
将路由按逻辑分组,但不影响 URL 路径:
你会发现,最终的 URL 中省略了带括号的文件夹(上图中的(marketing)
和(shop)
)。
借助路由组,即便在同一层级,也可以创建不同的布局:
在这个例子中,/account
、/cart
、/checkout
都在同一层级。但是 /account
和 /cart
使用的是 /app/(shop)/layout.js
布局和app/layout.js
布局,/checkout
使用的是 app/layout.js
创建多个根布局:
创建多个根布局,你需要删除掉 app/layout.js
文件,然后在每组都创建一个 layout.js
文件。创建的时候要注意,因为是根布局,所以要有 <html>
和 <body>
标签。
这个功能很实用,比如你将前台购买页面和后台管理页面都放在一个项目里,一个 C 端,一个 B 端,两个项目的布局肯定不一样,借助路由组,就可以轻松实现区分。
再多说几点:
(marketing)/about/page.js
和 (shop)/about/page.js
都会解析为 /about
,这会导致报错。app/layout.js
文件,访问 /
会报错,所以app/page.js
需要定义在其中一个路由组中。app/(shop)/layout.js
根布局的 /cart
跳转到使用 app/(marketing)/layout.js
根布局的 /blog
会导致页面重新加载(full page load)。注:当定义多个根布局的时候,使用 app/not-found.js
会出现问题。具体参考 《Next.js v14 如何为多个根布局自定义不同的 404 页面?竟然还有些麻烦》
平行路由可以使你在同一个布局中同时或者有条件的渲染一个或者多个页面(类似于 Vue 的插槽功能)。
举个例子,在后台管理页面,需要同时展示团队(team)和数据分析(analytics)页面:
平行路由的使用方式是将文件夹以 @
作为开头进行命名,比如在上图中就定义了两个插槽 @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
。
除了让它们同时展示,你也可以根据条件判断展示:
在这个例子中,先在布局中获取用户的登录状态,如果登录,显示 dashboard 页面,没有登录,显示 login 页面。这样做的一大好处就在于代码完全分离。
平行路由可以让你为每个路由定义独立的错误处理和加载界面:
注意我们描述 team 和 analytics 时依然用的是“页面”这个说法,因为它们就像书写正常的页面一样使用 page.js。除此之外,它们也能像正常的页面一样,添加子页面,比如我们在 @analytics
下添加两个子页面:/page-views
and /visitors
:
平行路由跟路由组一样,不会影响 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...
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!