Next.js 的路由为什么是这样的

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

点赞收藏支持、手留余香、与有荣焉,动动你发财的小手哟,感谢各位大佬能留下您的足迹。

往期热门精彩推荐

# 2024最新程序员接活儿搞钱平台盘点

解锁 JSON.stringify() 5 个鲜为人知的功能

解锁 JSON.stringify() 7 个鲜为人知的坑

如何去实现浏览器多窗口互动

面试相关热门推荐

前端万字面经------基础篇

前端万字面积------进阶篇

简述 pt、rpx、px、em、rem、%、vh、vw的区别

实战开发相关推荐

前端常用的几种加密方法

探索Web Worker在Web开发中的应用

不懂 seo 优化?一篇文章帮你了解如何去做 seo 优化

【实战篇】微信小程序开发指南和优化实践

前端性能优化实战

聊聊让人头疼的正则表达式

获取文件blob流地址实现下载功能

Vue 虚拟 DOM 搞不懂?这篇文章帮你彻底搞定虚拟 DOM

移动端相关推荐

移动端横竖屏适配与刘海适配

移动端常见问题汇总

聊一聊移动端适配

Git 相关推荐

通俗易懂的 Git 入门

git 实现自动推送

更多精彩详见:个人主页

相关推荐
y先森1 小时前
CSS3中的伸缩盒模型(弹性盒子、弹性布局)之伸缩容器、伸缩项目、主轴方向、主轴换行方式、复合属性flex-flow
前端·css·css3
前端Hardy1 小时前
纯HTML&CSS实现3D旋转地球
前端·javascript·css·3d·html
susu10830189111 小时前
vue3中父div设置display flex,2个子div重叠
前端·javascript·vue.js
IT女孩儿2 小时前
CSS查缺补漏(补充上一条)
前端·css
吃杠碰小鸡3 小时前
commitlint校验git提交信息
前端
虾球xz4 小时前
游戏引擎学习第20天
前端·学习·游戏引擎
我爱李星璇4 小时前
HTML常用表格与标签
前端·html
疯狂的沙粒4 小时前
如何在Vue项目中应用TypeScript?应该注意那些点?
前端·vue.js·typescript
小镇程序员4 小时前
vue2 src_Todolist全局总线事件版本
前端·javascript·vue.js
野槐4 小时前
前端图像处理(一)
前端