页面间的导航:<Link>
组件和 useRouter
作者:码力无边
在上一篇文章中,我们掌握了如何通过文件系统创建静态、嵌套和动态的页面。现在,我们的应用有了多个独立的"房间",但缺少连接它们的"走廊"。今天,我们将学习如何在这些页面之间建立流畅、高效的导航,这也是提升用户体验的关键一步。
为什么不能直接用 <a>
标签?
在传统的 HTML 中,我们使用 <a>
标签进行页面跳转:
html
<a href="/about">关于我们</a>
你完全可以在 Next.js 中这样做,但你会立刻发现一个问题:每次点击链接,浏览器都会进行一次完整的页面刷新。状态会丢失,整个应用会重新加载,这完全违背了我们使用 React 这样的单页应用(SPA)框架的初衷。我们追求的是那种无需刷新、如丝般顺滑的页面切换体验。
为了实现这种客户端导航 (Client-Side Navigation),Next.js 提供了两个核心工具:
<Link>
组件:用于声明式导航,是绝大多数情况下的首选。useRouter
钩子:用于编程式(或命令式)导航,在特定逻辑触发后执行跳转。
1. 声明式导航:<Link>
组件的核心用法
<Link>
组件是 Next.js 导航的基石。它在背后做了大量优化工作,以确保导航既快速又高效。
基础用法
要使用它,首先从 next/link
中导入,然后用它包裹一个 <a>
标签。
tsx
// 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
。
你可以使用模板字符串:
tsx
<Link href="/posts/hello-world">
<a>阅读第一篇文章</a>
</Link>
或者,为了更清晰和更强的类型支持,可以使用一个 URL 对象:
tsx
<Link
href={{
pathname: '/posts/[slug]',
query: { slug: 'hello-world' },
}}
>
<a>阅读第一篇文章</a>
</Link>
当 URL 结构变得复杂时,使用对象形式 href
会让代码更具可读性和可维护性。
2. 编程式导航:useRouter
钩子
有时候,我们不能简单地让用户点击一个链接来跳转。例如:
- 用户提交表单后,需要跳转到成功页面。
- 用户登录成功后,需要跳转到个人中心。
- 基于某些复杂的业务逻辑判断后,需要将用户重定向。
在这些场景下,我们需要在 JavaScript 代码中主动触发导航。useRouter
钩子就是为此而生。
基本跳转:router.push()
useRouter
钩子返回一个路由器(router)对象,该对象包含了路由信息和一些方法,其中最常用的就是 push
方法。
tsx
// 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 中的所有查询参数和动态路由参数。
tsx
// pages/posts/[slug].tsx
import { useRouter } from 'next/router';
function PostPage() {
const router = useRouter();
const { slug } = router.query; // 获取动态参数 slug
return <h1>文章:{slug}</h1>;
}
3. 干货满满:解锁 <Link>
和 useRouter
的高级功能
掌握了基础用法,我们再来深入挖掘一些能显著提升应用性能和用户体验的高级特性。
a) 预取(Prefetching):让导航快如闪电
这是 <Link>
组件最神奇的功能之一。默认情况下,当一个 <Link>
组件出现在用户的视口(viewport)中时,Next.js 会在后台自动预取链接指向页面的代码。
这意味着,当用户真正点击链接时,所需的所有资源都已准备就绪,页面切换几乎是瞬时的。这个小小的优化对用户体验的提升是巨大的。
在大多数情况下,你不需要做任何事,这个功能是自动开启的。如果你想禁用它(例如在一个有成百上千个链接的页面上),可以设置 prefetch={false}
。
tsx
<Link href="/very-large-page" prefetch={false}>
<a>一个非常大的页面(不预取)</a>
</Link>
b) 替换历史记录:replace
默认情况下,router.push()
和 <Link>
都会在浏览器的历史记录中添加一条新记录,这样用户可以点击"后退"按钮返回。
但在某些场景下,我们不希望用户能返回。例如,从登录页跳转到主页后,我们不希望用户能通过后退回到登录页。这时,可以使用 replace
选项。
使用 <Link>
:
tsx
<Link href="/dashboard" replace>
<a>登录</a>
</Link>
使用 useRouter
:
javascript
router.replace('/dashboard');
这会替换掉当前的历史记录,而不是新增一条。
c) 控制滚动行为:scroll
默认情况下,每次导航后,Next.js 都会将页面滚动到顶部。这符合大多数用户的预期。但如果你不希望发生滚动(例如,在一个带有标签页的页面内切换内容),可以设置 scroll={false}
。
tsx
<Link href="/?tab=settings" scroll={false}>
<a>切换到设置标签</a>
</Link>
当你点击这个链接时,URL 会改变,页面内容也会更新,但滚动条的位置会保持不变。
最佳实践与总结
-
内部导航用
<Link>
:只要是你的应用内部页面之间的跳转,并且是由用户直接点击触发的,就优先使用<Link>
组件,以享受预取等性能优化。 -
外部链接用
<a>
:如果要链接到其他网站,请直接使用标准的<a>
标签。 -
编程式跳转用
useRouter
:在事件处理函数、副作用(useEffect
)等需要用代码控制导航的场景下,使用useRouter
。 -
样式化
<Link>
:<Link>
组件本身不接受className
等样式属性,因为它不渲染任何可见的 DOM 元素。你应该将样式应用在它内部的<a>
标签或其他组件上。
tsx
// 使用 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 和全局样式。敬请期待!