Next.js 路由导航:四种方式构建流畅的页面跳转(三)

前言

在前端 Web 开发中,路由和导航系统是构建用户友好体验的关键部分。Next.js 15 为开发者提供了一套完整而强大的路由解决方案,不仅支持 声明式和命令式 的导航方式,还内置了 预加载、流式渲染 等性能优化机制,让我们能够轻松打造出流畅自然的页面跳转体验。通过合理运用这套路由系统,我们可以有效管理应用状态,提升用户体验。

本文介绍 Next.js 中的四大导航机制:

  1. <Link> 组件:Next.js 推荐的导航方式,支持预加载和客户端导航
  2. useRouter Hook:用于客户端组件中的编程式导航
  3. redirect 函数:专门用于服务端组件的重定向
  4. History API:用于高度定制化的 URL 操作

<Link> 组件

Next.js 的 <Link> 组件是 <a> 标签的增强版,它不仅继承了 <a> 标签的所有功能,还在此基础上增加了预获取(Prefetching)和客户端路由导航等高级特性。它是 Next.js 中实现页面导航最常用且推荐的方式,能够显著提升用户体验。

基础使用

使用 <Link> 组件进行导航非常简单,你只需要将 href 属性指向目标路由即可。例如,要导航到一个名为 /dashboard 的页面:

javascript 复制代码
import Link from 'next/link'
 
export default function Page() {
  return <Link href="/dashboard">Dashboard</Link>
}

动态路由与 <Link>

<Link> 组件也完美支持动态路由。你可以通过模板字符串或对象形式来构建动态链接:

javascript 复制代码
import Link from 'next/link'
 
export default function PostList({ posts }) {
  return (
    <ul>
      {posts.map((post) => (
        <li key={post.id}>
          <Link href={`/blog/${post.slug}`}>{post.title}</Link>
        </li>
      ))}
    </ul>
  )
}

Next.js 导航优化机制

<Link> 组件之所以能够提供如此流畅的导航体验,得益于 Next.js 内置的几项优化机制:

1. 预获取(Prefetching)

预获取是 Next.js 的一项核心优化功能。当用户点击链接时,Next.js 会在后台自动预加载链接指向页面的数据和代码。这意味着当用户真正点击链接时,页面几乎可以瞬间加载完成,大大减少了等待时间,提升了用户感知的性能。

  • 默认行为 :在生产环境中,<Link> 组件默认会预获取其指向的路由。当链接进入视口时,Next.js 会在后台静默地下载并解析目标页面的 JavaScript、CSS 和数据。

  • 禁用预获取 :如果你不希望某个链接进行预获取,可以设置 prefetch={false}

    javascript 复制代码
    import Link from 'next/link'
     
    export default function Page() {
      return <Link href="/dashboard" prefetch={false}>Dashboard</Link>
    }

禁用预获取通常用于那些不经常访问或数据量非常大的页面,以避免不必要的网络请求。

2. 流式渲染(Streaming)

流式渲染是 Next.js 13 引入的一项重要特性,它允许你将页面的 UI 分割成更小的、独立的单元,并逐步将它们从服务器发送到客户端。这意味着用户无需等待整个页面加载完成,就可以看到部分内容并开始交互,从而显著提升了初始加载性能和用户体验。

<Link> 组件与流式渲染结合使用时,可以实现更平滑的页面过渡。当用户导航到一个新页面时,Next.js 可以先渲染页面的骨架(例如 layout.js),然后逐步填充动态内容(例如通过 loading.jsSuspense)。

  • 结合 loading.js :当导航到新路由时,Next.js 会立即显示目标路由的 loading.js 文件中的 UI,而不是等待所有数据加载完成。这为用户提供了即时反馈,减少了白屏时间。

    jsx 复制代码
    // app/dashboard/loading.js
    export default function Loading() {
      return <h2>Loading dashboard...</h2>
    }
  • 结合 React Suspense :你可以在组件内部使用 <Suspense> 来包裹异步加载的内容。当异步内容加载时,Suspense 会显示一个 fallback UI,直到内容准备就绪。

    jsx 复制代码
    // app/dashboard/page.js
    import { Suspense } from 'react'
    import { PostFeed, Spinner } from './components'
     
    export default function Page() {
      return (
        <section>
          <Suspense fallback={<Spinner />}>
            <PostFeed />
          </Suspense>
        </section>
      )
    }

    这种方式使得页面在加载过程中保持响应,用户体验更加流畅。

3. 客户端过渡(Client-side transitions)

客户端过渡是指在客户端(浏览器)完成页面之间的导航,而不是每次都向服务器发送完整的页面请求。当用户点击 <Link> 组件时,Next.js 会拦截默认的浏览器导航行为,并通过 JavaScript 在客户端更新 URL 和渲染新的页面内容。这避免了浏览器重新加载整个页面,从而实现了快速、无刷新的页面切换。

  • 原理 :Next.js 利用 History API(pushStatereplaceState)来改变 URL,并结合 React 的组件渲染机制,只更新发生变化的组件部分,而不是重新渲染整个 DOM 树。

  • 优势

    • 速度快:无需重新加载整个页面,只更新必要部分,响应速度更快。
    • 用户体验好:页面切换平滑,没有闪烁或白屏。
    • 状态保持:在某些情况下,可以更好地保持页面状态(例如滚动位置、表单输入)。

跳转行为设置

App Router 的默认行为是滚动到新路由的顶部,或者在前进后退导航时维持之前的滚动距离。如果你想要禁用这个行为,可以给 <Link> 组件传递一个 scroll={false} 属性:

javascript 复制代码
// next/link
<Link href="/dashboard" scroll={false}>
  Dashboard
</Link>

或者在使用 router.pushrouter.replace 的时候,设置 scroll: false

javascript 复制代码
// useRouter
import { useRouter } from 'next/navigation'
 
const router = useRouter()
 
router.push('/dashboard', { scroll: false })

强制刷新行为

在某些情况下,你可能需要强制刷新页面以获取最新数据或重置组件状态。在 Next.js 13 及更高版本中,推荐使用 router.refresh() 方法来实现此目的,而不是依赖 <Link> 组件的 refresh 属性(该属性已被移除或不推荐使用)。

router.refresh() 会刷新当前路由,但会保留客户端状态(例如输入框内容、滚动位置),并重新请求服务器组件的数据。这对于需要重新验证数据的场景非常有用,例如用户提交表单后更新列表。

javascript 复制代码
'use client'
 
import { useRouter } from 'next/navigation'
 
export default function MyComponent() {
  const router = useRouter()
 
  const handleRefresh = () => {
    router.refresh() // 刷新当前路由
  }
 
  return (
    <button onClick={handleRefresh}>刷新数据</button>
  )
}

useRouter() Hook

useRouter 是 Next.js 提供的一个客户端 Hook,它允许你在客户端组件中以编程方式进行路由导航和访问路由信息。当你需要更复杂的导航逻辑,例如基于用户操作、表单提交或异步数据加载后进行跳转时,useRouter 是一个非常强大的工具。

基础使用

要使用 useRouter,首先需要在客户端组件中导入它,然后调用 useRouter() 来获取 router 实例:

javascript 复制代码
'use client'
 
import { useRouter } from 'next/navigation'
 
export default function Page() {
  const router = useRouter()
 
  return (
    <button type="button" onClick={() => router.push('/dashboard')}>
      Go to Dashboard
    </button>
  )
}

注意,useRouter 只能在客户端组件中使用,因此你的组件文件顶部需要有 'use client' 指令。

常用方法

router 实例提供了多种方法来控制路由:

  • router.push(href, options): 将新路由添加到历史堆栈,实现页面跳转。options 可以包含 scroll: false 来禁用滚动到顶部。

    javascript 复制代码
    router.push('/posts/123', { scroll: false });
  • router.replace(href, options): 替换历史堆栈中的当前路由,用户无法通过浏览器后退按钮返回上一个页面。options 同样可以包含 scroll: false

    javascript 复制代码
    router.replace('/login', { scroll: false });
  • router.back(): 返回历史堆栈中的上一个页面,类似于浏览器后退按钮。

  • router.forward(): 前进到历史堆栈中的下一个页面,类似于浏览器前进按钮。

  • router.refresh(): 刷新当前路由,重新请求服务器组件的数据,但保留客户端状态。这对于重新验证数据的场景非常有用。

  • router.prefetch(href): 手动预获取指定路由的数据和代码。当你希望在用户点击链接之前,提前加载某个页面资源时非常有用。

获取路由信息

useRouter 还提供了访问当前路由信息的能力:

  • router.pathname: 获取当前路由的路径名(例如 /blog/my-post)。
  • router.query: 获取当前路由的查询参数(例如 { id: '123' })。
  • router.asPath: 获取浏览器中显示的完整路径,包括查询参数(例如 /blog/my-post?id=123)。

示例:动态路由带参数

javascript 复制代码
'use client'
 
import { useRouter } from 'next/navigation'
 
export default function PostDetail({ postId }) {
  const router = useRouter()
 
  const navigateToComments = () => {
    router.push(`/posts/${postId}/comments`)
  }
 
  return (
    <div>
      <h1>Post {postId}</h1>
      <button onClick={navigateToComments}>View Comments</button>
    </div>
  )
}

redirect 函数

在 Next.js 的 App Router 中,redirect 函数是一个强大的服务器端 API,用于在服务器端组件或 Route Handlers 中进行重定向。与客户端的 useRouter 不同,redirect 在服务器上执行,这意味着它可以在页面渲染之前就将用户导向另一个 URL,这对于认证、权限检查或处理旧 URL 等场景非常有用。

基础使用

redirect 函数非常直观,你只需要传入目标 URL 即可:

javascript 复制代码
import { redirect } from 'next/navigation'
 
async function fetchTeam(id) {
  const res = await fetch('https://...')
  if (!res.ok) return undefined
  return res.json()
}
 
export default async function Profile({ params }) {
  const team = await fetchTeam(params.id)
  if (!team) {
    redirect('/login') // 如果团队不存在,重定向到登录页
  }
 
  // ... 渲染 Profile 页面内容
}

在上面的例子中,如果 fetchTeam 函数返回 undefined(表示团队不存在),用户将被立即重定向到 /login 页面,而不会渲染 Profile 页面的任何内容。

适用场景

  • 认证和授权:检查用户是否登录或是否有权限访问某个页面。如果未通过,则重定向到登录页或权限不足页。
  • 数据不存在:当请求的数据不存在时,重定向到 404 页面或错误页面。
  • URL 规范化:将旧的或不规范的 URL 重定向到新的规范 URL,有助于 SEO。
  • 表单提交后:在服务器端处理完表单提交后,重定向用户到成功页面或列表页面。

注意事项

  • redirect 只能在服务器端环境中使用(服务器组件、Route Handlers)。
  • 一旦调用 redirect,它会立即终止当前请求的渲染,并发送一个重定向响应给客户端。
  • redirect 会抛出一个特殊的错误,Next.js 会捕获并处理这个错误来执行重定向。因此,在调用 redirect 之后,你不需要再返回任何 JSX 或其他内容。

浏览器原生 History API

除了 Next.js 提供的 <Link> 组件和 useRouter Hook 外,你也可以直接使用浏览器原生的 History API (window.history.pushStatewindow.history.replaceState) 来操作浏览器历史记录,从而实现页面导航或 URL 更新。这种方式提供了最大的灵活性,但通常需要你手动处理更多的逻辑,例如状态管理和 UI 更新。

window.history.pushState

pushState 方法用于将一个新的状态(state)和 URL 添加到浏览器的历史堆栈中。这意味着用户可以通过浏览器的后退按钮返回到上一个状态。

  • 语法window.history.pushState(state, unused, url)
    • state: 一个与新历史记录条目关联的状态对象。当用户导航到新状态时,popstate 事件会被触发,并且该状态对象会作为事件的 state 属性。
    • unused: 历史记录的标题。现代浏览器通常会忽略此参数,因此通常传入空字符串或 null
    • url: 新的历史记录条目的 URL。如果未指定,则默认为当前 URL。

示例:对列表进行排序

假设你有一个产品列表,并希望通过 URL 查询参数来控制排序方式,但又不想触发完整的页面刷新:

javascript 复制代码
'use client'
 
import { useSearchParams } from 'next/navigation'
 
export default function SortProducts() {
  const searchParams = useSearchParams()
 
  function updateSorting(sortOrder) {
    const params = new URLSearchParams(searchParams.toString())
    params.set('sort', sortOrder)
    // 使用 pushState 更新 URL,但不刷新页面
    window.history.pushState(null, '', `?${params.toString()}`)
  }
 
  return (
    <>
      <button onClick={() => updateSorting('asc')}>Sort Ascending</button>
      <button onClick={() => updateSorting('desc')}>Sort Descending</button>
    </>
  )
}

点击按钮后,URL 会变为 ?sort=asc?sort=desc,但页面不会重新加载,你可以根据 searchParams 的变化来更新 UI。

window.history.replaceState

replaceState 方法用于修改当前历史记录条目的状态和 URL,而不是添加新的条目。这意味着用户无法通过浏览器后退按钮返回到修改前的状态。

  • 语法window.history.replaceState(state, unused, url)
    • 参数与 pushState 相同。

示例:切换地域设置(国际化)

在国际化应用中,你可能希望用户切换语言时,URL 发生变化,但又不希望用户能够通过后退按钮回到旧的语言版本:

javascript 复制代码
'use client'
 
import { usePathname } from 'next/navigation'
 
export default function LocaleSwitcher() {
  const pathname = usePathname()
 
  function switchLocale(locale) {
    // e.g. '/en/about' or '/fr/contact'
    const newPath = `/${locale}${pathname}`
    // 使用 replaceState 替换当前 URL,用户无法后退到旧语言版本
    window.history.replaceState(null, '', newPath)
  }
 
  return (
    <>
      <button onClick={() => switchLocale('en')}>English</button>
      <button onClick={() => switchLocale('fr')}>French</button>
    </>
  )
}

注意事项

  • 手动处理 UI 更新 :使用 History API 更改 URL 不会自动触发 React 组件的重新渲染。你需要结合 usePathnameuseSearchParams 或其他状态管理方案来监听 URL 变化并手动更新 UI。
  • 状态管理 :当使用 pushStatereplaceState 时,你需要自行管理与 URL 状态相关的组件状态。
  • 不推荐作为主要导航方式 :对于 Next.js 应用中的常规页面导航,强烈推荐使用 <Link> 组件和 useRouter Hook,因为它们提供了内置的优化和更好的开发体验。History API 更适用于需要精细控制 URL 但不涉及完整页面导航的场景,例如 URL 参数过滤、模态框状态等。

总结

本篇文章详细介绍了 Next.js 15 中实现路由和链接导航的四种主要方式:<Link> 组件、useRouter Hook、redirect 函数以及浏览器原生的 History API。探讨了每种方式的基础用法、高级特性、适用场景以及背后的原理。

  • <Link> 组件 :作为 Next.js 推荐的导航方式,它通过预获取、流式渲染和客户端过渡等机制,提供了极致流畅的用户体验。掌握其 hrefprefetchscroll 等属性,能让你轻松构建高性能的页面跳转。
  • useRouter Hook :为客户端组件提供了编程式导航的能力,通过 pushreplacebackforwardrefresh 等方法,你可以灵活控制页面跳转逻辑,并获取当前的路由信息。
  • redirect 函数:作为服务器端重定向的利器,它在服务器组件或 Route Handlers 中发挥作用,适用于认证、权限控制和 URL 规范化等场景,确保用户在渲染前就被导向正确的位置。
  • 浏览器原生 History API :虽然不如 Next.js 内置方案便捷,但 pushStatereplaceState 提供了对浏览器历史记录最底层的控制,适用于需要精细化 URL 操作而不触发完整页面刷新的特定场景。
相关推荐
杨荧2 小时前
基于大数据的美食视频播放数据可视化系统 Python+Django+Vue.js
大数据·前端·javascript·vue.js·spring boot·后端·python
cmdyu_2 小时前
如何解决用阿里云效流水线持续集成部署Nuxt静态应用时流程卡住,进行不下去的问题
前端·经验分享·ci/cd
WordPress学习笔记2 小时前
根据浏览器语言判断wordpress访问不同语言的站点
前端·javascript·html
yuanmenglxb20043 小时前
解锁webpack核心技能(二):配置文件和devtool配置指南
前端·webpack·前端工程化
啃火龙果的兔子3 小时前
React 多语言(i18n)方案全面指南
前端·react.js·前端框架
阿奇__4 小时前
深度修改elementUI样式思路
前端·vue.js·elementui
小白白一枚1114 小时前
css的选择器
前端·css
盛夏绽放4 小时前
SassSCSS:让CSS拥有超能力的预处理器
前端·css·rust
xw55 小时前
支付宝小程序IDE突然极不稳定
前端·支付宝