当前位置: 首页 > news >正文

页面间的导航:`<Link>` 组件和 `useRouter`

页面间的导航:<Link> 组件和 useRouter

作者:码力无边


在上一篇文章中,我们掌握了如何通过文件系统创建静态、嵌套和动态的页面。现在,我们的应用有了多个独立的“房间”,但缺少连接它们的“走廊”。今天,我们将学习如何在这些页面之间建立流畅、高效的导航,这也是提升用户体验的关键一步。

为什么不能直接用 <a> 标签?

在传统的 HTML 中,我们使用 <a> 标签进行页面跳转:

<a href="/about">关于我们</a>

你完全可以在 Next.js 中这样做,但你会立刻发现一个问题:每次点击链接,浏览器都会进行一次完整的页面刷新。状态会丢失,整个应用会重新加载,这完全违背了我们使用 React 这样的单页应用(SPA)框架的初衷。我们追求的是那种无需刷新、如丝般顺滑的页面切换体验。

为了实现这种客户端导航 (Client-Side Navigation),Next.js 提供了两个核心工具:

  1. <Link> 组件:用于声明式导航,是绝大多数情况下的首选。
  2. useRouter 钩子:用于编程式(或命令式)导航,在特定逻辑触发后执行跳转。

1. 声明式导航:<Link> 组件的核心用法

<Link> 组件是 Next.js 导航的基石。它在背后做了大量优化工作,以确保导航既快速又高效。

基础用法

要使用它,首先从 next/link 中导入,然后用它包裹一个 <a> 标签。

// pages/index.tsx
import Link from 'next/link';export default function HomePage() {return (<div><h1>欢迎来到首页</h1><nav><Link href="/about"><a>关于我们</a></Link><br /><Link href="/contact"><a>联系我们</a></Link></nav></div>);
}

注意:在旧版本的 Next.js 中,<Link> 必须包裹一个 <a> 标签。从 Next.js 13 的 app 目录开始,你可以不再需要手动添加 <a> 标签。但在我们目前学习的 pages 目录中,包裹 <a> 标签仍然是推荐的最佳实践。

现在点击链接,你会发现页面内容瞬间切换,而浏览器标签页并没有出现刷新的加载动画。这就是客户端导航的魔力!

导航到动态路由

链接到动态页面也同样简单。假设我们要链接到一篇 slug 为 hello-world 的博客文章,其页面文件是 pages/posts/[slug].tsx

你可以使用模板字符串:

<Link href="/posts/hello-world"><a>阅读第一篇文章</a>
</Link>

或者,为了更清晰和更强的类型支持,可以使用一个 URL 对象:

<Linkhref={{pathname: '/posts/[slug]',query: { slug: 'hello-world' },}}
><a>阅读第一篇文章</a>
</Link>

当 URL 结构变得复杂时,使用对象形式 href 会让代码更具可读性和可维护性。

2. 编程式导航:useRouter 钩子

有时候,我们不能简单地让用户点击一个链接来跳转。例如:

  • 用户提交表单后,需要跳转到成功页面。
  • 用户登录成功后,需要跳转到个人中心。
  • 基于某些复杂的业务逻辑判断后,需要将用户重定向。

在这些场景下,我们需要在 JavaScript 代码中主动触发导航。useRouter 钩子就是为此而生。

基本跳转:router.push()

useRouter 钩子返回一个路由器(router)对象,该对象包含了路由信息和一些方法,其中最常用的就是 push 方法。

// components/LoginForm.tsx
import { useRouter } from 'next/router';export default function LoginForm() {const router = useRouter();const handleSubmit = (event) => {event.preventDefault();// ... 假设这里处理了登录逻辑 ...const isLoginSuccess = true; // 模拟登录成功if (isLoginSuccess) {// 登录成功,跳转到仪表盘页面router.push('/dashboard');}};return (<form onSubmit={handleSubmit}>{/* ... 表单输入框 ... */}<button type="submit">登录</button></form>);
}

获取路由参数

useRouter 不仅仅能用于导航。在上一篇文章中,我们用它来获取动态路由的参数。这里再回顾一下,router.query 对象包含了 URL 中的所有查询参数和动态路由参数。

// pages/posts/[slug].tsx
import { useRouter } from 'next/router';function PostPage() {const router = useRouter();const { slug } = router.query; // 获取动态参数 slugreturn <h1>文章:{slug}</h1>;
}

3. 干货满满:解锁 <Link>useRouter 的高级功能

掌握了基础用法,我们再来深入挖掘一些能显著提升应用性能和用户体验的高级特性。

a) 预取(Prefetching):让导航快如闪电

这是 <Link> 组件最神奇的功能之一。默认情况下,当一个 <Link> 组件出现在用户的视口(viewport)中时,Next.js 会在后台自动预取链接指向页面的代码

这意味着,当用户真正点击链接时,所需的所有资源都已准备就绪,页面切换几乎是瞬时的。这个小小的优化对用户体验的提升是巨大的。

在大多数情况下,你不需要做任何事,这个功能是自动开启的。如果你想禁用它(例如在一个有成百上千个链接的页面上),可以设置 prefetch={false}

<Link href="/very-large-page" prefetch={false}><a>一个非常大的页面(不预取)</a>
</Link>

b) 替换历史记录:replace

默认情况下,router.push()<Link> 都会在浏览器的历史记录中添加一条新记录,这样用户可以点击“后退”按钮返回。

但在某些场景下,我们不希望用户能返回。例如,从登录页跳转到主页后,我们不希望用户能通过后退回到登录页。这时,可以使用 replace 选项。

使用 <Link>:

<Link href="/dashboard" replace><a>登录</a>
</Link>

使用 useRouter:

router.replace('/dashboard');

这会替换掉当前的历史记录,而不是新增一条。

c) 控制滚动行为:scroll

默认情况下,每次导航后,Next.js 都会将页面滚动到顶部。这符合大多数用户的预期。但如果你不希望发生滚动(例如,在一个带有标签页的页面内切换内容),可以设置 scroll={false}

<Link href="/?tab=settings" scroll={false}><a>切换到设置标签</a>
</Link>

当你点击这个链接时,URL 会改变,页面内容也会更新,但滚动条的位置会保持不变。

最佳实践与总结

  1. 内部导航用 <Link>:只要是你的应用内部页面之间的跳转,并且是由用户直接点击触发的,就优先使用 <Link> 组件,以享受预取等性能优化。

  2. 外部链接用 <a>:如果要链接到其他网站,请直接使用标准的 <a> 标签。

  3. 编程式跳转用 useRouter:在事件处理函数、副作用(useEffect)等需要用代码控制导航的场景下,使用 useRouter

  4. 样式化 <Link><Link> 组件本身不接受 className 等样式属性,因为它不渲染任何可见的 DOM 元素。你应该将样式应用在它内部的 <a> 标签或其他组件上。

// 使用 CSS Modules
import styles from './MyLink.module.css';<Link href="/about"><a className={styles.link}>关于</a>
</Link>

今天,我们不仅学会了如何在 Next.js 页面间创建基本的导航,还深入了解了背后的性能优化机制和高级用法。一个好的导航系统是应用的骨架,而 Next.js 已经为我们提供了坚固且高效的工具。

现在我们的应用有了结构和导航,是时候让它变得好看了。在下一篇文章中,我们将探讨在 Next.js 中样式化的各种方法,包括 CSS 模块、Tailwind CSS 和全局样式。敬请期待!


文章转载自:

http://75p6nQPi.skbbt.cn
http://P0XQefrp.skbbt.cn
http://FVj6ulpG.skbbt.cn
http://PSwSm55b.skbbt.cn
http://nK4O3eVn.skbbt.cn
http://cwKyXp2J.skbbt.cn
http://Sos01Zmv.skbbt.cn
http://VfENnnb8.skbbt.cn
http://ItaXmydC.skbbt.cn
http://syZQD4sp.skbbt.cn
http://RENHvCRg.skbbt.cn
http://ckD7Ll9O.skbbt.cn
http://Zf7dSKcq.skbbt.cn
http://UvnAxCKp.skbbt.cn
http://luzQQ6dR.skbbt.cn
http://0KhfubfX.skbbt.cn
http://nlZGontY.skbbt.cn
http://mkJq1t2K.skbbt.cn
http://iRpvjP2M.skbbt.cn
http://7TqdSHQ3.skbbt.cn
http://HM2TMifW.skbbt.cn
http://qcBOetXa.skbbt.cn
http://SHRXMbQZ.skbbt.cn
http://nrgPsmmX.skbbt.cn
http://biZXMN7O.skbbt.cn
http://XyrqIP8q.skbbt.cn
http://Vt4TJABZ.skbbt.cn
http://3zCrcS3L.skbbt.cn
http://sxyOGOTX.skbbt.cn
http://du0TyMKC.skbbt.cn
http://www.dtcms.com/a/371255.html

相关文章:

  • 视频动作识别-VideoSwin
  • AI 自然语音对话接入客服系统的场景分析及实现
  • 【基础-判断】架构设计时需要考虑“一次开发,多端部署”,这样可以节省跨设备UI开发工作量,同时提升应用部署的伸缩性。
  • [光学原理与应用-428]:非线性光学 - 为什么要改变光的波长/频率,获得特点波长/频率的光?
  • 运筹学——求解线性规划的单纯形法
  • HTML标签之超链接
  • MySQL问题5
  • MyBatis Example模式SQL注入风险
  • C语言数据结构——详细讲解《二叉树与堆的基本概念》
  • 【杂类】I/O
  • import type在模块引入中的作用
  • MySQL入门指南:从安装到工作原理
  • 【基础-判断】一个页面可以存在多个@Entry修饰的组件。
  • MapStruct详解
  • 新的打卡方式
  • GESP 7/8级免CSP-J/S初赛!申请注意事项!今年已过,明年提前关注!
  • esbuild入门
  • 决策树概念与原理
  • More Effective C++ 条款31:让函数根据多个对象来决定怎么虚拟
  • Python列表:从入门到灵活运用的全攻略
  • 校园洒水车cad+三维图+设计说书
  • 机械硬盘的工作原理
  • 生命周期方法:didUpdateWidget
  • Pie Menu Editor V1.18.7.exe 怎么安装?详细安装教程(附安装包)​
  • ragflow MCP 调用核心提示词解析:逻辑闭环与优化方向
  • Knative Serving:ABP 应用的 scale-to-zero 与并发模型
  • Xsens帮助独立工作室创造引人注目的冒险游戏真实角色动画
  • 《动手学深度学习v2》学习笔记 | 2.4 微积分 2.5 自动微分
  • 【开题答辩全过程】以 哈尔滨裕丰草莓园管理系统为例,包含答辩的问题和答案
  • 国内外支持个人开发者的应用市场