Next.js 在 2016 年发布时的竞争优势之一是其内置的路由系统。它同时支持客户端和服务器端渲染,因此开发人员无需配置像 React Router DOM 这样的第三方路由库。Next.js 的路由器也是基于文件系统的,这意味着应用程序中的路由由文件和文件夹的组织方式决定。这使得它对大多数开发人员更具吸引力。
在本文中,我们将探讨什么是平行和相交路由,将它们与现有的路由选项进行比较,了解它们的约定,并演示如何使用它们。
什么是并行路由?
并行路由是 Next.js 中一种新的高级路由约定。根据文档:
"并行路由是一种 Next.js 路由范例,它允许您同时或有条件地在同一布局中渲染一个或多个页面,这些页面可以独立导航。"
换句话说,并行路由允许您在同一视图中渲染多个页面。
并行路由在渲染应用程序的复杂动态部分时最有用,例如在具有多个独立部分或模态的仪表板中。
下图是 Next 文档中的仪表板页面的插图,演示了并行路由的复杂性:
在这种情况下,@team
和@analytics
路由使用并行路由同时呈现为仪表板布局的部分。
如何使用平行路由
并行路由是使用@folder
约定定义的,也称为"槽",本质上是一个文件夹,@
其名称前面带有符号:
槽位在路由段内定义,并用作不同类型动态内容的容器。一旦定义,它们就可以作为相应路由段内文件中的道具轻松访问。layout.tsx
例如,假设我们有一个仪表板页面,并希望使用并行路由模块化地组织其内容。第一步是在目录中定义命名槽。定义并行路由:app/dashboard
为简单起见,我们将在插槽中包含占位符内容,如下所示:
tsx
// app/dashboard/@team/page.tsx
export default function Team() {
return (
<h2>Team slot</h2>
<svg>...</svg>
)
}
// app/dashboard/@revenue/page.tsx
export default function Revenue() {
return (
<h2>Revenue slot</h2>
<svg>...</svg>
)
}
// app/dashboard/@analytics/page.tsx
export default function Analytics() {
return (
<h2>Analytics slot</h2>
<svg>...</svg>
)
}
定义插槽后,layout.tsx
仪表板路径段中的文件现在接受@analytics
、@revenue
和@team
插槽作为属性。这取代了导入它们的传统方法。
因此,如果我们转到该layout.tsx
文件并将props
对象记录到控制台,我们将得到以下结果:
ts
{
analytics: {
...
},
},
revenue: {
...
},
},
teams: {
...
},
},
children: {
...
}
}
下一步涉及将插槽作为props
对象的属性进行访问,并在布局中动态渲染它们,如下所示:
tsx
import React from "react";
interface ISlots {
children: React.ReactNode;
analytics: React.ReactNode;
team: React.ReactNode;
revenue: React.ReactNode;
}
export default function DashboardLayout(props: ISlots) {
return (
<div>
<h1>{props.children}</h1>
<div>{props.analytics}</div>
<div>{props.team}</div>
<div >{props.revenue}</div>
</div>
);
}
当你导航时,你应该会看到使用并行路由呈现的仪表板布局:localhost:3000/dashboard
首先,上边定义的插槽充当隐式插槽,专门设计用于在 /dashboard
路由段内呈现 page.tsx
文件的内容。因此,它不需要映射到文件夹:
这意味着相当于.dashboard/page.tsx
等同于 dashboard/@children/page.tsx
其次,可能很容易假设分析、团队和收入时段充当路由段,因为它们的结构相似。 但是,它们不会影响 URL 结构,并且像 app/dashboard/@team/members
这样的文件路径仍然可以在 localhost:3000/dashboard/members
上访问。
为什么要使用平行路由?
与传统方法相比,并行路由的明显优势是能够使用插槽在同一 URL
上和同一视图内呈现完全独立的代码。
传统上,开发人员在动态渲染页面上的内容时面临着限制,因为传统的路由机制仅支持线性渲染,即每个视图一个 URL
。
这就是多年来组件组合开发受到欢迎的原因。 它支持渲染模块化和可重用的组件,这些组件可以组合和组合以构建复杂的用户界面。
如果我们要在仪表板示例中使用组件组合方法,@analytics
、@team
和 @revenue
插槽将被定义为组件并在布局中排列,如下所示:
tsx
import UserAnalytics from "@/components/Team";
import RevenueMetrics from "@/components/Analytics";
import Notifications from "@/components/Revenue";
export default function DashboardLayout({
children,
}: {
children: React.ReactNode;
}) {
return (
<>
<div>{children}</div>
<UserAnalytics />
<Revenue />
<Team />
</>
);
}
虽然这种方法很有效并且有助于使代码更易于管理,特别是当多个团队正在处理该项目时,但可以使用并行路由实现相同的结果,并具有独立流和子导航的额外好处。
独立路由流
每个并行路由都独立流式传输到布局,允许单独的加载和错误状态,与布局的其他部分完全隔离。
例如,如果像分析这样的部分比其他仪表板部分需要更长的加载时间,则可以单独为该部分显示加载指示器,而其余部分保持完全交互。
我们可以通过在每个槽中定义loading.tsx
和error.tsx
文件来实现这一点,如下图所示:
然后,我们为状态添加相应的内容。 例如,我们可以为加载状态添加一个加载微调器,为错误状态添加一个自定义界面。 但为了简单起见,我们可以简单地添加一段文字:
tsx
export default function Loading() {
return <div>Loading...</div>;
}
如果我们向槽的加载时间添加不同的延迟,我们可以观察到此功能的实际效果,如下所示:
tsx
// wait function to add varying load time
export function wait(time: number) {
return new Promise((resolve) => {
setTimeout(resolve, time);
});
}
// app/dashboard/@team/page.tsx
export default async function Team() {
Await wait(1000)
return (
<h2>Team slot</h2>
<svg>...</svg>
)
}
// app/dashboard/@revenue/page.tsx
export default async function Revenue() {
Await wait(2000)
return (
<h2>Revenue slot</h2>
<svg>...</svg>
)
}
// app/dashboard/@analytics/page.tsx
export default async function Analytics() {
Await wait(3000)
return (
<h2>Analytics slot</h2>
<svg>...</svg>
)
}
我们会得到以下结果:
请注意,为了使此功能正常工作,您还必须为子插槽定义一个 loading.tsx
文件,即在 /dashboard
路径的根目录中:
子导航
槽的独立属性超出了加载和错误状态。 每个路由都作为一个独立的实体运行,并具有自己的状态管理和导航,从而使用户界面的每个部分作为独立的应用程序运行。
这意味着我们可以在槽内创建与dashboard
/@folder/
子文件夹文件路径关联的子文件夹,并来回导航,而无需更改仪表板上其他部分的状态或呈现。
例如,如果我们希望在@team
槽中实现子导航,我们可以创建一个子文件夹,如下所示:
然后,我们在 @team
插槽中提供一个链接:localhost:3000/dashboard/members
,该链接可导航到 Members
子文件夹,并且在 Members
子文件夹中提供另一个链接:localhost:3000/dashboard
,该链接可导航回团队默认视图:
tsx
import React from "react";
import Card from "@/components/card/card";
import { wait } from "@/lib/wait/page";
import Link from "next/link";
// app/dashboard/@team
export default async function Team() {
return (
<>
<h2>Teams slot</h2>
<svg>...</svg>
<Link href="/dashboard/members">
<p> Got to /members page </p>
</Link>
</>
);
}
// app/dashboard/@team/members
export default function Members() {
return (
<>
<h1>Members page</h1>
<Link href="/dashboard">
<p> Got back to /teams page </p>
</Link>
</>
);
}
不匹配的路由
当槽内的内容与当前 URL 不匹配时,就会出现不匹配的路由。 如上一节所示,当进行子导航时,并且仪表板或布局中只有一个部分与新路由匹配时,就会发生这种情况。
更简单地说,默认情况下,每个槽都与定义它们的路由段的文件路径对齐。
但是,在客户端导航期间,与我们在上一节中所做的类似,文件路径更改为dashboard/members
,仅匹配 @teams 插槽。 因此,@analytics
和 @revenue
位置变得无与伦比。
发生这种情况的原因是,在页面重新加载期间,Next.js
尝试在不匹配的槽内呈现 default.tsx
文件。 如果该文件不存在,Next.js
会抛出 404
错误; 否则,它将呈现文件的内容。
default.tsx
文件用作不匹配插槽的后备,允许我们在 Next.js
无法检索插槽的活动状态时呈现替代内容。
为了防止 Next.js
在访问 @team
槽中的 /dashboard/members
路由时抛出 404 错误,我们只需为路由段中的每个槽(包括子槽)添加一个 default.tsx
文件:
现在,当我们对路由进行硬导航时,页面将正确加载并呈现不匹配路由的默认视图:dashboard/members
有条件的路由
并行路由也可以根据某些条件有条件地渲染。 例如,如果我们希望只有经过身份验证的用户才能访问,如果用户已通过身份验证,我们可以使用身份验证状态来呈现仪表板,否则,我们可以使用身份验证状态来呈现登录槽:
tsx
interface ISlots {
children: React.ReactNode;
analytics: React.ReactNode;
team: React.ReactNode;
revenue: React.ReactNode;
login: React.ReactNode
}
export default function DashboardLayout(props: ISlots) {
const isLoggedIn = true; // Simulates auth state
if (!isLoggedIn) return props.login;
return(
<>
{children}
{users}
{revenue}
{notifications}
</>
);
}
什么是拦截路由?
拦截路由是 Next.js 路由范例,它允许我们从当前上下文或布局中应用程序的另一部分加载路由。
拦截路由的概念很简单; 它本质上充当中间件,使我们能够在实际导航发生之前拦截路由请求。
考虑登录模式或照片源。 传统上,单击导航栏中的登录链接或照片源中的图像会将您引导至完全呈现登录组件或图像的专用页面。
然而,通过拦截路由,我们可以改变这种行为。 通过拦截路由、屏蔽它并将其覆盖在当前 URL 上,我们可以将其渲染为覆盖布局的模式,而无需切换上下文:
一旦路由被拦截,Next.js 就会保留拦截的路由,使其可共享。 但是,如果发生硬导航(例如,浏览器刷新)或通过可共享 URL 进行访问,Next.js 会呈现整个页面而不是模态页面。 在这种情况下,不会发生路由拦截。
如何创建拦截路由
拦截路由遵循类似于并行路由的约定,使用 (.)
文件夹约定。 此约定涉及在文件夹名称中添加 (.)
前缀,以匹配同一级别上的现有路由段。
例如,假设我们有一个带有嵌套动态路由的 app/products
路由段:/[item]
,可通过 localhost:3000/products/itemId
访问:
我们可以通过在产品段中创建一个 (.)[item]
目录来拦截从 localhost:3000/products
到 localhost:3000/products/itemId
的导航,如下图所示:
然后,我们定义当路由被拦截时我们想要渲染的内容,例如:
tsx
interface IimageProps {
params: {
item: string;
};
}
export default async function Page({ params: { item } }: IimageProps) {
const res = await getImage(item);
const image = await res.json();
return (
<>
<div>
<div>
<div>
<Image
src={image.urls.regular}
alt={image.alt_description}
priority
fill
style={{ borderRadius: "10px" }}
/>
</div>
</div>
<p>{image.alt_description}</p>
</div>
</>
);
}
目前,如果尝试通过 /products
路由访问任何项目的单独页面,则 URL
将更新为 localhost:3000/products/itemId
,并且 /products/(.)[item]
的内容 呈现拦截的路由,替换预期项目的内容:
上面的例子有两点需要注意。 首先,项目的页面在页面重新加载后呈现,其次,拦截路由呈现为独立页面而不是模式。
默认情况下,拦截路由会部分渲染。 因此,如果页面重新加载或直接访问 localhost:3000/products/itemId
URL,则会呈现 /products/[item]
的内容。
虽然相交的路由看起来好像是作为独立页面呈现的,但事实并非如此,因为上下文保持不变; 正如前面所解释的,它仅在页面重新加载后发生变化。
为了确保路由正确呈现为模态,并具有背景和必要的特征,我们需要在并行路由中定义拦截路由。 为此,我们首先在 /products
路由中创建一个槽并将 (.)[item]
拦截路由移入其中:
接下来,我们将继续使用以下代码将一个layout.tsx
文件添加到/products
目录中,并在@modal
槽中添加一个default.tsx
文件:
tsx
// app/products/layout.tsx
import React from "react";
export default function layout({
children,
modal,
}: {
children: React.ReactNode;
modal: React.ReactNode;
}) {
return (
<div>
{children}
{modal}
</div>
);
}
// app/products/@modal/default.tsx
Export const Default = () => {
return null;
};
我们定义了 default.tsx
文件来防止 Next.js
在模态未激活时抛出 404
错误,并且因为我们不想在模态未激活时显示任何内容,所以我们返回 null
。
现在有了正确的样式,模态应该在拦截后正确呈现:
默认情况下,向后导航会关闭模态框,但如果您希望向模态框添加执行此操作的图标或按钮,则可以使用 router.back()
,如下面的代码所示:
tsx
'use client'
import { useRouter } from 'next/navigation'
export default function Page() {
const router = useRouter()
return (
<div>
<span onClick={() => router.back()}>Close modal</span>
...
</div>
)
}
拦截模式
拦截路由约定的工作方式与相对路径约定../类似,这意味着我们可以使用不同级别定义拦截路由:
- (..) 匹配同一级别的段
- (..)(..) 匹配上面两级的段
- (...) 匹配根级别的段
通过这些模式,我们可以在应用程序中的任何位置拦截路由。
结论
并行和拦截路由是 Next.js 中的高级路由机制,它们在构建 Web 应用程序时单独提供增强的灵活性和改进的用户体验。 然而,当组合起来时,它们提供了更高级的功能。
点赞收藏支持、手留余香、与有荣焉,动动你发财的小手哟,感谢各位大佬能留下您的足迹。
往期热门精彩推荐
面试相关热门推荐
实战开发相关推荐
移动端相关推荐
Git 相关推荐
更多精彩详见:个人主页