路由跳转
前言
上篇我们介绍了如何定义路由,本篇我们讲讲如何在 Next.js 中实现链接和导航。
所谓“导航”,指的是使用 JavaScript 进行页面切换,通常会比浏览器默认的重新加载更快,因为在导航的时候,只会更新必要的组件,而不会重新加载整个页面。
在 Next.js 中,有 4 种方式可以实现路由导航:
使用
<Link>
组件使用
useRouter
Hook(客户端组件)使用
redirect
函数(服务端组件)使用浏览器原生 History API
使用 <Link>
组件
Next.js 的<Link>
组件是一个拓展了原生 HTML <a>
标签的内置组件,用来实现预获取数据和客户端路由导航。这是 Next.js 中路由导航的主要和推荐方式。
基础使用
- 基本的方式如下:
ts
import Link from "next/link";
export default function Page() {
return <Link href="/dashboard">Dashboard</Link>;
}
- 支持动态渲染
ts
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>
);
}
- 获取当前路径名
如果需要对当前链接进行判断,你可以使用 usePathname()
它会读取当前 URL 的路径名(pathname
)。示例代码如下:
ts
"use client"; //必须要加 usePathname()只能在客户端使用
import { usePathname } from "next/navigation";
import Link from "next/link";
export function Navigation({ navLinks }) {
const pathname = usePathname(); // 获取到当前路径 比如:/dashboard
return (
<>
{navLinks.map((link) => {
const isActive = pathname === link.href;
return (
<Link
className={isActive ? "text-blue" : "text-black"}
href={link.href}
key={link.name}
>
{link.name}
</Link>
);
})}
</>
);
}
- 跳转行为设置
App Router 的默认行为是滚动到新路由的顶部,或者在前进后退导航时维持之前的滚动距离。
如果你想要禁用这个行为,你可以给 <Link>
组件传递一个 scroll={false}
属性,或者在使用 router.push
和 router.replace
的时候,设置 scroll: false
开始标签跳转
ts
// next/link
<Link href="/dashboard" scroll={false}>
Dashboard
</Link>
开始方法跳转
ts
// useRouter
"use client";
import { useRouter } from "next/navigation";
const router = useRouter();
router.push("/dashboard", { scroll: false });
useRouter() hook
方法跳转
ts
"use client";
import { useRouter } from "next/navigation";
export default function Page() {
const router = useRouter();
return (
<button type="button" onClick={() => router.push("/dashboard")}>
Dashboard
</button>
);
}
注意使用该 hook
需要在客户端组件中。(顶层的 'use client' 就是声明这是客户端组件)
redirect() 函数
客户端组件使用 useRouter hook
,服务端组件则可以直接使用 redirect
函数,这也是 Next.js 提供的 API,使用示例代码如下:
ts
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");
}
// ...
}
History API
- 比如用 pushState 对列表进行排序:
ts
"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);
window.history.pushState(null, "", `?${params.toString()}`);
}
return (
<>
<button onClick={() => updateSorting("asc")}>Sort Ascending</button>
<button onClick={() => updateSorting("desc")}>Sort Descending</button>
</>
);
}
- replaceState 会替换浏览器历史堆栈的当前条目,替换后用户无法后退,比如切换应用的地域设置(国际化):
ts
"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}`;
window.history.replaceState(null, "", newPath);
}
return (
<>
<button onClick={() => switchLocale("en")}>English</button>
<button onClick={() => switchLocale("fr")}>French</button>
</>
);
}