Next.js 在 2016 年发布时的竞争优势之一是其内置的路由系统。它同时支持客户端和服务器端渲染,因此开发人员无需配置像 React Router DOM 这样的第三方路由库。Next.js 的路由器也是基于文件系统的,这意味着应用程序中的路由由文件和文件夹的组织方式决定。这使得它对大多数开发人员更具吸引力。

Vercel 团队一直在通过每个新版本改进路由系统。Next.js 9 引入了 API 路由,让开发人员可以创建处理特定 API 端点的无服务器函数。Next.js 13 引入了 App Router,这是一种新的路由约定,可让您在同一应用程序中渲染客户端和服务器端 React 组件。

App Router 具有许多功能,包括布局、动态路由、嵌套路由以及一组称为并行和相交路由的新路由约定。这些功能可用于创建高级路由模式。

在本文中,我们将探讨什么是平行和相交路线,将它们与现有的路线选项进行比较,了解它们的约定,并演示如何使用它们。


先决条件

预先了解 Next.js 将有助于阅读本文,但如果您对 React 有深入的了解,则不需要了解这些知识

什么是并行路由?


并行路由是 Next.js 中一种新的高级路由约定。根据文档:

“并行路由是一种 Next.js 路由范例,它允许您同时或有条件地在同一布局中渲染一个或多个页面,这些页面可以独立导航。”

换句话说,并行路由允许您在同一视图中渲染多个页面。

并行路由在渲染应用程序的复杂动态部分时最有用,例如在具有多个独立部分或模态的仪表板中。

下图是 Next 文档中的仪表板页面的插图,演示了并行路线的复杂性:

在这种情况下,@team@analytics路线使用并行路由同时呈现为仪表板布局的部分。

并行路由使用@folder约定来定义,也称为"插槽",本质上是一个以@符号为前缀的文件夹:

插槽定义在路由段内,用作容器来包含不同类型的动态内容。一旦定义,它们就可以在相应路由段内的layout.tsx文件中作为props轻松访问。
例如,假设我们有一个仪表板页面,想要使用并行路由模块化地组织其内容。第一步是在app/dashboard目录中为team, analytics, and revenue部分定义命名插槽:
在仪表板中渲染的page.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插槽作为props,这取代了传统的导入方式。
因此,如果我们进入layout.tsx文件并将props对象记录到控制台,我们将得到以下结果:
{analytics: {...},},revenue: {...},},teams: {...},},children: {...}}

下一步是访问props对象的插槽属性,并在布局中动态渲染它们,如下所示:

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时,你应该会看到用并行路由渲染的仪表板布局:

从这个例子中还有一些其他细节需要注意。首先,除了定义的team、analytics和revenue插槽之外,还有一个children插槽,它是一个专门设计用于渲染/dashboard路由段内page.tsx文件内容的隐式插槽。因此,它不需要映射到文件夹:


在仪表板中渲染的page.tsx文件内容,意味着dashboard/page.tsx相当于dashboard/@children/page.tsx。
其次,你可能会认为analytics、team和revenue插槽充当路由段,访问它会改变URL路径。但是,它们并不会影响URL结构,文件路径如app/dashboard/@team/members还是可通过localhost:3000/dashboard/members访问。
为什么要使用并行路由? 
与传统方法相比,并行路由的明显优势在于能够使用插槽在同一URL和视图中渲染完全独立的代码。
传统上,开发人员在动态渲染页面内容时面临限制,因为传统的路由机制只支持线性渲染 - 意味着一个URL对应一个视图。
这就是为什么多年来组件组合开发被采用的原因。它支持渲染模块化和可重用的组件,这些组件可以组合构造复杂的用户界面。
如果我们在仪表板示例中使用组件组合方法,@analytics、@team和@revenue插槽将被定义为组件,并在仪表板布局中排列如下:
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文件来实现这一点,如下图所示:
在仪表板中定义loading.tsx和error.tsx文件


然后,我们为这些状态添加相应的内容。例如,我们可以为加载状态添加一个加载微调器,为错误状态添加一个自定义界面。但为简单起见,我们可以只添加文本:
export default function Loading() {return <div>Loading...</div>;}

如果我们为插槽的加载时间添加不同的延迟,我们可以观察到这一特性的实际效果:

// 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> ) }


我们将得到以下结果:实施了不同延迟的仪表板


请注意,为了让此功能正常工作,你还必须为children插槽(即/dashboard路径的根目录)定义一个loading.tsx文件:

子导航

插槽的独立属性不仅局限于加载和错误状态。每个路由都像一个独立的实体一样运行,完全拥有自己的状态管理和导航,因此用户界面的每个部分(在这种情况下是仪表板)都像一个独立的应用程序一样运行。
这意味着我们可以在插槽内创建与dashboard/@folder/sub-folder文件路径相关联的子文件夹,并在其中来回导航,而不会改变其他仪表板部分的状态或渲染。
例如,如果我们希望在@team插槽内实现子导航,我们可以创建一个子文件夹,如下所示:

在仪表板中实现子导航
然后,我们在@team插槽中添加一个链接:localhost:3000/dashboard/members,导航到members子文件夹,以及在members子文件夹中添加另一个链接:localhost:3000/dashboard,返回到team的默认视图:
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> </> ); }

注意,在某些情况下,当尝试导航回默认视图时,即/dashboard,你可能会遇到黑屏。这只是开发模式下的问题;如果你构建项目并运行生产版本,一切应该都可以正常工作。

未匹配的路由 
当插槽内的内容与当前URL不匹配时,就会发生未匹配路由。这种情况发生在子导航时,如上一节所示,只有仪表板的一个部分或布局与新路由匹配。
更简单地说,默认情况下,每个插槽都与定义它们的路由段的文件路径对齐。在我们的仪表板示例中,这就是/dashboard。
然而,在客户端导航期间,类似于我们在上一节中所做的,文件路径会变为dashboard/members,只与@teams插槽匹配。因此,@analytics和@revenue插槽变成了未匹配状态。
这是因为在页面重载时,Next.js试图在未匹配的插槽中渲染default.tsx文件。如果文件不存在,Next.js会抛出404错误;否则,它会渲染文件的内容。
default.tsx文件为未匹配的插槽提供了fallback ,允许我们在Next.js无法检索到插槽的活动状态时渲染替代内容。
为了防止Next.js在访问@team插槽内的/dashboard/members路由时抛出404错误,我们只需为该路由段内的每个插槽(包括children插槽)添加一个default.tsx文件:


在路由段内为每个插槽添加Default.tsx文件
现在,当我们进行到dashboard/members路由的硬导航时,页面将正确加载并为未匹配的路由渲染默认视图:

条件路由 

并行路由也可以根据某些条件有条件地渲染。例如,如果我们只希望经过身份验证的用户才能访问仪表板,我们可以使用身份验证状态,如果用户通过身份验证则渲染仪表板,否则渲染登录插槽:

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将渲染整个页面而不是模态。在这种情况下,不会发生路由拦截。
如何创建拦截路由 
拦截路由遵循与并行路由类似的约定,使用(.)folder约定。该约定包括在文件夹名称前添加(.)前缀,以匹配同一级别上现有的路由段。
例如,假设我们有一个app/products路由段,其中包含一个嵌套的动态路由:/[item],可通过localhost:3000/products/itemId访问:

我们可以通过在products段中创建一个(.)[item]目录来拦截从localhost:3000/products到localhost:3000/products/itemId的导航,如下图所示:


然后,我们定义当路由被拦截时要渲染的内容,如下所示:


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 文件:
// 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 。

现在有了正确的样式,模态应该在拦截后正确呈现:

默认情况下,向后导航会关闭Modal,但如果您希望向模式添加执行此操作的图标或按钮,可以使用 router.back() ,如下面的代码所示:

'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 应用程序时单独提供增强的灵活性和改进的用户体验。然而,当组合起来时,它们提供了更高级的功能,如本文所示。

并行和拦截路由是 Next.js 中的高级路由机制,它们在构建 Web 应用程序时单独提供增强的灵活性和改进的用户体验。然而,当组合起来时,它们提供了更高级的功能,如本文所示。







点赞(1) 打赏

评论列表 共有 0 条评论

暂无评论

服务号

订阅号

备注【拉群】

商务洽谈

微信联系站长

发表
评论
立即
投稿
返回
顶部