Next.js 鏈接和導(dǎo)航教程:四種導(dǎo)航方式與路由原理

2025-03-21 18:51 更新

在 Next.js 中,有四種方式可以在路由之間導(dǎo)航:

  • 使用 <Link> 組件
  • 使用 useRouter 鉤子(客戶端組件)
  • 使用 redirect 函數(shù)(服務(wù)器組件)
  • 使用原生 History API

本頁(yè)將介紹如何使用這些選項(xiàng),并深入探討導(dǎo)航的工作原理。

<Link> 組件

<Link> 是一個(gè)內(nèi)置組件,它擴(kuò)展了 HTML 的 <a> 標(biāo)簽,提供了路由之間的預(yù)取和客戶端導(dǎo)航。它是 Next.js 中推薦的路由導(dǎo)航方式。

你可以從 next/link 導(dǎo)入 <Link>,并通過(guò)傳遞 href 屬性來(lái)使用它:

import Link from 'next/link'


export default function Page() {
  return <Link href="/dashboard">儀表盤</Link>
}

<Link> 還可以傳遞其他可選屬性,更多信息請(qǐng)參考 API 文檔。

useRouter() 鉤子

useRouter 鉤子允許你在客戶端組件中通過(guò)代碼更改路由。

'use client'


import { useRouter } from 'next/navigation'


export default function Page() {
  const router = useRouter()


  return (
    <button type="button" onClick={() => router.push('/dashboard')}>
      儀表盤
    </button>
  )
}

如需了解 useRouter 的所有方法,請(qǐng)參考 API 文檔。

建議:除非有特定需求,否則建議使用 <Link& 組件進(jìn)行路由導(dǎo)航。

redirect 函數(shù)

對(duì)于服務(wù)器組件,應(yīng)使用 redirect 函數(shù)。

import { redirect } from 'next/navigation'


async function fetchTeam(id: string) {
  const res = await fetch('https://...')
  if (!res.ok) return undefined
  return res.json()
}


export default async function Profile({
  params,
}: {
  params: Promise<{ id: string }>
}) {
  const { id } = await params
  if (!id) {
    redirect('/login')
  }


  const team = await fetchTeam(id)
  if (!team) {
    redirect('/join')
  }


  // ...
}

需要注意的是

- redirect 默認(rèn)返回 307(臨時(shí)重定向)狀態(tài)碼。在服務(wù)器操作中使用時(shí),它返回 303(參見(jiàn)其他),這通常用于 POST 請(qǐng)求后的成功頁(yè)面重定向。
- redirect 內(nèi)部會(huì)拋出錯(cuò)誤,因此不應(yīng)在 try/catch 塊中調(diào)用。
- 在客戶端組件的渲染過(guò)程中可以調(diào)用 redirect,但不能在事件處理程序中使用。此時(shí)可以改用 useRouter 鉤子。
- redirect 也接受絕對(duì) URL,可用于重定向到外部鏈接。
- 如果希望在渲染前重定向,可以使用 next.config.js 或中間件。

如需更多信息,請(qǐng)參考 redirect API 文檔。

使用原生 History API

Next.js 允許你使用原生的 window.history.pushStatewindow.history.replaceState 方法,在不重新加載頁(yè)面的情況下更新瀏覽器歷史記錄。

pushStatereplaceState 調(diào)用會(huì)集成到 Next.js 路由器中,使你可以與 usePathnameuseSearchParams 同步。

window.history.pushState

使用它向?yàn)g覽器歷史記錄棧中添加新條目。用戶可以返回到之前的頁(yè)面。例如,對(duì)產(chǎn)品列表進(jìn)行排序:

'use client'


import { useSearchParams } from 'next/navigation'


export default function SortProducts() {
  const searchParams = useSearchParams()


  function updateSorting(sortOrder: string) {
    const params = new URLSearchParams(searchParams.toString())
    params.set('sort', sortOrder)
    window.history.pushState(null, '', `?${params.toString()}`)
  }


  return (
    <>
      <button onClick={() => updateSorting('asc')}>升序排序</button>
      <button onClick={() => updateSorting('desc')}>降序排序</button>
    </>
  )
}

window.history.replaceState

使用它替換瀏覽器歷史記錄棧中的當(dāng)前條目。用戶無(wú)法返回到之前的頁(yè)面。例如,切換應(yīng)用的語(yǔ)言:

'use client'


import { usePathname } from 'next/navigation'


export function LocaleSwitcher() {
  const pathname = usePathname()


  function switchLocale(locale: string) {
    // 例如:'/en/about' 或 '/fr/contact'
    const newPath = `/${locale}${pathname}`
    window.history.replaceState(null, '', newPath)
  }


  return (
    <>
      <button onClick={() => switchLocale('en')}>英語(yǔ)</button>
      <button onClick={() => switchLocale('zh')}>中文</button>
    </>
  )
}

路由與導(dǎo)航的工作原理

應(yīng)用路由器采用混合方式處理路由和導(dǎo)航。在服務(wù)器端,應(yīng)用代碼會(huì)根據(jù)路由段自動(dòng)進(jìn)行代碼分割。在客戶端,Next.js 會(huì)預(yù)取并緩存路由段。這意味著,當(dāng)用戶導(dǎo)航到新路由時(shí),瀏覽器不會(huì)重新加載頁(yè)面,只有更改的路由段會(huì)重新渲染,從而提升導(dǎo)航體驗(yàn)和性能。

1. 代碼分割

代碼分割允許將應(yīng)用代碼拆分為更小的bundle,供瀏覽器下載和執(zhí)行。這減少了每個(gè)請(qǐng)求的數(shù)據(jù)傳輸量和執(zhí)行時(shí)間,從而提升性能。

服務(wù)器組件允許應(yīng)用代碼按路由段自動(dòng)進(jìn)行代碼分割。這意味著導(dǎo)航時(shí)只會(huì)加載當(dāng)前路由所需的代碼。

2. 預(yù)取

預(yù)取是在用戶訪問(wèn)頁(yè)面之前預(yù)先加載路由的一種方式。

在 Next.js 中,路由通過(guò)以下兩種方式預(yù)取:

  • <Link> 組件:當(dāng)路由進(jìn)入用戶視口時(shí),會(huì)自動(dòng)預(yù)取。預(yù)取在頁(yè)面首次加載或通過(guò)滾動(dòng)進(jìn)入視口時(shí)發(fā)生。
  • router.prefetch()useRouter 鉤子可用于程序化預(yù)取路由。

<Link> 的默認(rèn)預(yù)取行為(即未指定或設(shè)置為 null 時(shí))取決于你對(duì) loading.js 的使用。只有共享布局,沿著渲染的“組件樹(shù)”直到第一個(gè) loading.js 文件,才會(huì)被預(yù)取并緩存30秒。這減少了獲取整個(gè)動(dòng)態(tài)路由的成本,并且你可以顯示即時(shí)加載狀態(tài),為用戶提供更好的視覺(jué)反饋。

你可以通過(guò)將 prefetch 屬性設(shè)置為 false 來(lái)禁用預(yù)取?;蛘?,你可以通過(guò)將 prefetch 屬性設(shè)置為 true 來(lái)預(yù)取加載邊界之外的完整頁(yè)面數(shù)據(jù)。

如需更多信息,請(qǐng)參考 <Link> API 文檔。

需要注意的是

- 預(yù)取在開(kāi)發(fā)環(huán)境中不會(huì)啟用,僅在生產(chǎn)環(huán)境中啟用。

3. 緩存

Next.js 有一個(gè)稱為路由器緩存的內(nèi)存客戶端緩存。當(dāng)用戶在應(yīng)用中導(dǎo)航時(shí),預(yù)取的路由段和已訪問(wèn)路由的 React 服務(wù)器組件負(fù)載會(huì)存儲(chǔ)在緩存中。

這意味著在導(dǎo)航時(shí),會(huì)盡可能重復(fù)使用緩存,而不是向服務(wù)器發(fā)送新請(qǐng)求——通過(guò)減少請(qǐng)求數(shù)量和數(shù)據(jù)傳輸量來(lái)提升性能。

了解更多關(guān)于路由器緩存的工作原理以及如何配置它。

4. 部分渲染

部分渲染意味著只有在導(dǎo)航時(shí)更改的路由段會(huì)在客戶端重新渲染,共享的段將被保留。

例如,在兩個(gè)兄弟路由 /dashboard/settings/dashboard/analytics 之間導(dǎo)航時(shí),settings 頁(yè)面將被卸載,analytics 頁(yè)面將以前端狀態(tài)掛載,共享的 dashboard 布局將被保留。在同一個(gè)動(dòng)態(tài)段的兩個(gè)路由之間導(dǎo)航時(shí),也會(huì)出現(xiàn)這種行為。例如,使用 /blog/[slug]/page/blog/first 導(dǎo)航到 /blog/second

Next.js 鏈接和導(dǎo)航-部分渲染

沒(méi)有部分渲染時(shí),每次導(dǎo)航都會(huì)導(dǎo)致整個(gè)頁(yè)面在客戶端重新渲染。只渲染更改的段減少了數(shù)據(jù)傳輸量和執(zhí)行時(shí)間,從而提升性能。

5. 軟導(dǎo)航

瀏覽器在頁(yè)面之間導(dǎo)航時(shí)會(huì)執(zhí)行“硬導(dǎo)航”。Next.js 應(yīng)用路由器啟用了頁(yè)面之間的“軟導(dǎo)航”,確保只有更改的路由段會(huì)重新渲染(部分渲染)。這使得客戶端 React 狀態(tài)在導(dǎo)航期間得以保留。

6. 返回和前進(jìn)導(dǎo)航

默認(rèn)情況下,Next.js 將維護(hù)返回和前進(jìn)導(dǎo)航的滾動(dòng)位置,并在路由器緩存中重復(fù)使用路由段。

7. 在 pages/app/ 之間路由

在從 pages/ 逐步遷移到 app/ 時(shí),Next.js 路由器將自動(dòng)處理兩者之間的硬導(dǎo)航。為了檢測(cè)從 pages/app/ 的過(guò)渡,有一個(gè)客戶端路由器過(guò)濾器,它利用應(yīng)用路由的概率檢查,這偶爾會(huì)導(dǎo)致誤報(bào)。默認(rèn)情況下,這種誤報(bào)的概率非常低(0.01%)。此概率可通過(guò) next.config.js 中的 experimental.clientRouterFilterAllowedRate 選項(xiàng)進(jìn)行調(diào)整。需要注意的是,降低誤報(bào)率會(huì)增加客戶端bundle中生成的過(guò)濾器的大小。

或者,如果你希望完全禁用此處理并手動(dòng)管理 pages/app/ 之間的路由,可以在 next.config.js 中將 experimental.clientRouterFilter 設(shè)置為 false。當(dāng)此功能被禁用時(shí),默認(rèn)情況下,pages 中的任何與 app 路由重疊的動(dòng)態(tài)路由都不會(huì)被正確導(dǎo)航到。

以上內(nèi)容是否對(duì)您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號(hào)
微信公眾號(hào)

編程獅公眾號(hào)