Next.js 在 2016 年發(fā)布時的競爭優(yōu)勢之一是其內(nèi)置的路由系統(tǒng)。它同時支持客戶端和服務(wù)器端渲染,因此開發(fā)人員無需配置像 React Router DOM 這樣的第三方路由庫。Next.js 的路由器也是基于文件系統(tǒng)的,這意味著應用程序中的路由由文件和文件夾的組織方式?jīng)Q定。這使得它對大多數(shù)開發(fā)人員更具吸引力。

      Vercel 團隊一直在通過每個新版本改進路由系統(tǒng)。Next.js 9 引入了 API 路由,讓開發(fā)人員可以創(chuàng)建處理特定 API 端點的無服務(wù)器函數(shù)。Next.js 13 引入了 App Router,這是一種新的路由約定,可讓您在同一應用程序中渲染客戶端和服務(wù)器端 React 組件。

      App Router 具有許多功能,包括布局、動態(tài)路由、嵌套路由以及一組稱為并行和相交路由的新路由約定。這些功能可用于創(chuàng)建高級路由模式。

      在本文中,我們將探討什么是平行和相交路線,將它們與現(xiàn)有的路線選項進行比較,了解它們的約定,并演示如何使用它們。


      先決條件

      預先了解 Next.js 將有助于閱讀本文,但如果您對 React 有深入的了解,則不需要了解這些知識

      什么是并行路由?


      并行路由是 Next.js 中一種新的高級路由約定。根據(jù)文檔:

      “并行路由是一種 Next.js 路由范例,它允許您同時或有條件地在同一布局中渲染一個或多個頁面,這些頁面可以獨立導航。”

      換句話說,并行路由允許您在同一視圖中渲染多個頁面。

      并行路由在渲染應用程序的復雜動態(tài)部分時最有用,例如在具有多個獨立部分或模態(tài)的儀表板中。

      下圖是 Next 文檔中的儀表板頁面的插圖,演示了并行路線的復雜性:

      在這種情況下,@team@analytics路線使用并行路由同時呈現(xiàn)為儀表板布局的部分。

      并行路由使用@folder約定來定義,也稱為"插槽",本質(zhì)上是一個以@符號為前綴的文件夾:

      插槽定義在路由段內(nèi),用作容器來包含不同類型的動態(tài)內(nèi)容。一旦定義,它們就可以在相應路由段內(nèi)的layout.tsx文件中作為props輕松訪問。
      例如,假設(shè)我們有一個儀表板頁面,想要使用并行路由模塊化地組織其內(nèi)容。第一步是在app/dashboard目錄中為team, analytics, and revenue部分定義命名插槽:
      在儀表板中渲染的page.tsx文件內(nèi)容

      為簡單起見,我們將在插槽中包含占位符內(nèi)容,如下所示:

      // 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> )}
      插槽定義后,儀表板路由段內(nèi)的layout.tsx文件現(xiàn)在接受@analytics、@revenue和@team插槽作為props,這取代了傳統(tǒng)的導入方式。
      因此,如果我們進入layout.tsx文件并將props對象記錄到控制臺,我們將得到以下結(jié)果:
      {analytics: {...},},revenue: {...},},teams: {...},},children: {...}}

      下一步是訪問props對象的插槽屬性,并在布局中動態(tài)渲染它們,如下所示:

      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時,你應該會看到用并行路由渲染的儀表板布局:

      從這個例子中還有一些其他細節(jié)需要注意。首先,除了定義的team、analytics和revenue插槽之外,還有一個children插槽,它是一個專門設(shè)計用于渲染/dashboard路由段內(nèi)page.tsx文件內(nèi)容的隱式插槽。因此,它不需要映射到文件夾:


      在儀表板中渲染的page.tsx文件內(nèi)容,意味著dashboard/page.tsx相當于dashboard/@children/page.tsx。
      其次,你可能會認為analytics、team和revenue插槽充當路由段,訪問它會改變URL路徑。但是,它們并不會影響URL結(jié)構(gòu),文件路徑如app/dashboard/@team/members還是可通過localhost:3000/dashboard/members訪問。
      為什么要使用并行路由??
      與傳統(tǒng)方法相比,并行路由的明顯優(yōu)勢在于能夠使用插槽在同一URL和視圖中渲染完全獨立的代碼。
      傳統(tǒng)上,開發(fā)人員在動態(tài)渲染頁面內(nèi)容時面臨限制,因為傳統(tǒng)的路由機制只支持線性渲染 - 意味著一個URL對應一個視圖。
      這就是為什么多年來組件組合開發(fā)被采用的原因。它支持渲染模塊化和可重用的組件,這些組件可以組合構(gòu)造復雜的用戶界面。
      如果我們在儀表板示例中使用組件組合方法,@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 /></> );}


      雖然這種方法有效,并且可以幫助使代碼更易于管理,尤其是在多個團隊合作的項目中,但使用并行路由也可以達到相同的效果,并且可以獨立流式傳輸和子導航。
      獨立路由流?
      每個并行路由都獨立流式傳輸?shù)讲季?允許單獨加載和錯誤狀態(tài),完全與布局的其他部分隔離。
      例如,如果分析部分加載時間比儀表板的其他部分長,則可以僅為該部分顯示加載指示器,而其他部分則保持完全交互狀態(tài)。
      我們可以通過在每個插槽中定義loading.tsx和error.tsx文件來實現(xiàn)這一點,如下圖所示:
      在儀表板中定義loading.tsx和error.tsx文件


      然后,我們?yōu)檫@些狀態(tài)添加相應的內(nèi)容。例如,我們可以為加載狀態(tài)添加一個加載微調(diào)器,為錯誤狀態(tài)添加一個自定義界面。但為簡單起見,我們可以只添加文本:
      export default function Loading() {return <div>Loading...</div>;}

      如果我們?yōu)椴宀鄣募虞d時間添加不同的延遲,我們可以觀察到這一特性的實際效果:

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


      我們將得到以下結(jié)果:實施了不同延遲的儀表板


      請注意,為了讓此功能正常工作,你還必須為children插槽(即/dashboard路徑的根目錄)定義一個loading.tsx文件:

      子導航

      插槽的獨立屬性不僅局限于加載和錯誤狀態(tài)。每個路由都像一個獨立的實體一樣運行,完全擁有自己的狀態(tài)管理和導航,因此用戶界面的每個部分(在這種情況下是儀表板)都像一個獨立的應用程序一樣運行。
      這意味著我們可以在插槽內(nèi)創(chuàng)建與dashboard/@folder/sub-folder文件路徑相關(guān)聯(lián)的子文件夾,并在其中來回導航,而不會改變其他儀表板部分的狀態(tài)或渲染。
      例如,如果我們希望在@team插槽內(nèi)實現(xiàn)子導航,我們可以創(chuàng)建一個子文件夾,如下所示:

      在儀表板中實現(xiàn)子導航
      然后,我們在@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,你可能會遇到黑屏。這只是開發(fā)模式下的問題;如果你構(gòu)建項目并運行生產(chǎn)版本,一切應該都可以正常工作。

      未匹配的路由?
      當插槽內(nèi)的內(nèi)容與當前URL不匹配時,就會發(fā)生未匹配路由。這種情況發(fā)生在子導航時,如上一節(jié)所示,只有儀表板的一個部分或布局與新路由匹配。
      更簡單地說,默認情況下,每個插槽都與定義它們的路由段的文件路徑對齊。在我們的儀表板示例中,這就是/dashboard。
      然而,在客戶端導航期間,類似于我們在上一節(jié)中所做的,文件路徑會變?yōu)閐ashboard/members,只與@teams插槽匹配。因此,@analytics和@revenue插槽變成了未匹配狀態(tài)。
      這是因為在頁面重載時,Next.js試圖在未匹配的插槽中渲染default.tsx文件。如果文件不存在,Next.js會拋出404錯誤;否則,它會渲染文件的內(nèi)容。
      default.tsx文件為未匹配的插槽提供了fallback?,允許我們在Next.js無法檢索到插槽的活動狀態(tài)時渲染替代內(nèi)容。
      為了防止Next.js在訪問@team插槽內(nèi)的/dashboard/members路由時拋出404錯誤,我們只需為該路由段內(nèi)的每個插槽(包括children插槽)添加一個default.tsx文件:


      在路由段內(nèi)為每個插槽添加Default.tsx文件
      現(xiàn)在,當我們進行到dashboard/members路由的硬導航時,頁面將正確加載并為未匹配的路由渲染默認視圖:

      條件路由?

      并行路由也可以根據(jù)某些條件有條件地渲染。例如,如果我們只希望經(jīng)過身份驗證的用戶才能訪問儀表板,我們可以使用身份驗證狀態(tài),如果用戶通過身份驗證則渲染儀表板,否則渲染登錄插槽:

      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的一種路由范式,允許我們從應用程序的另一部分加載當前上下文或布局中的路由。
      攔截路由的概念很簡單;它本質(zhì)上充當中間件,使我們能夠在實際導航發(fā)生之前攔截路由請求。
      考慮登錄模態(tài)或照片源。傳統(tǒng)上,單擊導航欄中的登錄鏈接或照片中的圖像會將您定向到完全渲染登錄組件或圖像的專用頁面。
      但是,通過攔截路由,我們可以改變這種行為。通過攔截路由、屏蔽它并將其覆蓋在當前URL上,我們可以將其渲染為覆蓋布局而不切換上下文的模態(tài):

      一旦路由被攔截,Next.js就會保留被攔截的路由,使其可共享。但是,如果發(fā)生硬導航(例如瀏覽器刷新)或通過可共享的URL訪問,Next.js將渲染整個頁面而不是模態(tài)。在這種情況下,不會發(fā)生路由攔截。
      如何創(chuàng)建攔截路由?
      攔截路由遵循與并行路由類似的約定,使用(.)folder約定。該約定包括在文件夾名稱前添加(.)前綴,以匹配同一級別上現(xiàn)有的路由段。
      例如,假設(shè)我們有一個app/products路由段,其中包含一個嵌套的動態(tài)路由:/[item],可通過localhost:3000/products/itemId訪問:

      我們可以通過在products段中創(chuàng)建一個(.)[item]目錄來攔截從localhost:3000/products到localhost:3000/products/itemId的導航,如下圖所示:


      然后,我們定義當路由被攔截時要渲染的內(nèi)容,如下所示:


      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] 的內(nèi)容呈現(xiàn)攔截的路線,替換預期項目的內(nèi)容

      從上面的示例可以注意到兩件事。首先,在頁面重新加載后,項目的頁面會被渲染;其次,攔截路由被渲染為獨立頁面,而不是模態(tài)。
      默認情況下,攔截路由是部分渲染的。因此,如果發(fā)生頁面重載或直接訪問localhost:3000/products/itemId URL,那么/products/[item]的內(nèi)容將被渲染。
      雖然看起來好像交叉路由被渲染為獨立頁面,但實際上并非如此,因為上下文保持不變;只有在頁面重新加載后才會發(fā)生變化,如前所述。
      為了確保路由正確地渲染為帶有背景和必要特征的模態(tài),我們需要在并行路由中定義攔截路由。為此,我們首先在/products路由中創(chuàng)建一個插槽,并將(.)[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 在模態(tài)未激活時拋出 404 錯誤,并且因為我們不想在模態(tài)未激活時顯示任何內(nèi)容,所以我們返回 null 。

      現(xiàn)在有了正確的樣式,模態(tài)應該在攔截后正確呈現(xiàn):

      默認情況下,向后導航會關(guān)閉Modal,但如果您希望向模式添加執(zhí)行此操作的圖標或按鈕,可以使用 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> )}

      截模式

      攔截路由約定的工作方式與相對路徑約定 ../ 類似,這意味著我們可以使用不同級別定義攔截路由:

      (..)?匹配同一級別的段

      (..)(..)?匹配上面兩級的段

      (...)?匹配根級別的段

      通過這些模式,我們可以在應用程序中的任何位置攔截路由。


      結(jié)論

      并行和攔截路由是 Next.js 中的高級路由機制,它們在構(gòu)建 Web 應用程序時單獨提供增強的靈活性和改進的用戶體驗。然而,當組合起來時,它們提供了更高級的功能,如本文所示。

      并行和攔截路由是 Next.js 中的高級路由機制,它們在構(gòu)建 Web 應用程序時單獨提供增強的靈活性和改進的用戶體驗。然而,當組合起來時,它們提供了更高級的功能,如本文所示。







      點贊(1) 打賞

      評論列表 共有 0 條評論

      暫無評論

      服務(wù)號

      訂閱號

      備注【拉群】

      商務(wù)洽談

      微信聯(lián)系站長

      發(fā)表
      評論
      立即
      投稿
      返回
      頂部