链接和导航
Next.js 中有四种方式在路由间导航:
- 使用
<Link>组件 - 使用
useRouterhook (客户端组件) - 使用
redirectfunction (服务器端组件) - 使用 native History API
本页将使您了解如何使用上面选项中的每一项,并且深入了解导航如何工作。
<Link> 组件
<Link> 是一个扩展 HTML <a>标签的内置组件,提供路由间的prefetching 和客户端导航。 它是 Next.js 中首要的和推荐的路由间导航方式。
您可以通过从next/link中导入使用,并向此组件传递一个href属性:
import Link from "next/link";
export default function Page() {
return <Link href="/dashboard">Dashboard</Link>;
}
import Link from "next/link";
export default function Page() {
return <Link href="/dashboard">Dashboard</Link>;
}
有其它可传入<Link>的可选属性。参阅API reference了解更多。
例子
链接到动态区段
链接到动态区段时, 您可以使用模版字面量和插值来生成链接列表。例如,博客文章列表:
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>
);
}
核对 active 链接
您能够使用usePathname()来确定链接是否处于 active 状态。例如,为了向 ative 链接添加 class,你能够核对当前路径名是否匹配链接的href:
"use client";
import { usePathname } from "next/navigation";
import Link from "next/link";
export function Links() {
const pathname = usePathname();
return (
<nav>
<Link className={`link ${pathname === "/" ? "active" : ""}`} href="/">
Home
</Link>
<Link
className={`link ${pathname === "/about" ? "active" : ""}`}
href="/about"
>
About
</Link>
</nav>
);
}
"use client";
import { usePathname } from "next/navigation";
import Link from "next/link";
export function Links() {
const pathname = usePathname();
return (
<nav>
<Link className={`link ${pathname === "/" ? "active" : ""}`} href="/">
Home
</Link>
<Link
className={`link ${pathname === "/about" ? "active" : ""}`}
href="/about"
>
About
</Link>
</nav>
);
}
滚动到id
Next.js APP Router 的默认行为是滚动到新路由的顶部或为前进或后退导航保持位置。
如果您想在导航时滚动到具体的id,您可以在您的 URL 后附加一个#hash 链接或者只是传递一个 hash 链接到href属性。这能够从<Link>渲染到一个<a>元素。
<Link href="/dashboard#settings">Settings</Link>
// Output
<a href="/dashboard#settings">Settings</a>
您需要知道:
- 如果导航时在视口中页面不可见,Next.js 将滚动到页面。
禁止滚动恢复
Next.js APP Router 的默认行为是滚动到新路由的顶部或为前进或后退导航保持位置。如果您想禁用此行为,您能够向<Link>组件传递scroll={false},或者向router.push() 或 router.replace()传 scroll: false`。
// next/link
<Link href="/dashboard" scroll={false}>
Dashboard
</Link>
// useRouter
import { useRouter } from "next/navigation";
const router = useRouter();
router.push("/dashboard", { scroll: false });
useRouter() hook
The useRouter hook allows you to programmatically change routes from Client Components.
"use client";
import { useRouter } from "next/navigation";
export default function Page() {
const router = useRouter();
return (
<button type="button" onClick={() => router.push("/dashboard")}>
Dashboard
</button>
);
}
useRouter方法的完整列表, 请参阅 API reference.
建议: 使用
<Link>组件在路由间导航,除非您有使用useRouter的具体需求.
redirect function
对于 服务器组件, 使用redirect function 代替.
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: { id: string } }) {
const team = await fetchTeam(params.id);
if (!team) {
redirect("/login");
}
// ...
}
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");
}
// ...
}
您需要知道:
- 默认情况下,
redirect返回 307 (暂时重定向)状态码。在服务器操作中使用时,它返回 303 (参阅其他), 303 通常作为 Post 请求重定向到成功页的结果。redirect能够在渲染过程中由客户端组件调用,但不能在时间处理中调用。您可以使用useRouterhook 代替。redirect也接受绝对 URLs,并且能重定向到外部链接。- 如果您想在渲染前重定向,使用
next.config.js或 Middleware.
参阅 redirect API reference 了解更多。
使用本地 History API
Next.js 允许您使用本地 window.history.pushState 和 window.history.replaceState 方法来更新浏览器历史栈而无需重新加载页面。
pushState 和 replaceState集成在 Next.js 路由中, 您可以同步使用usePathname 和 useSearchParams.
window.history.pushState
使用它向浏览器的历史栈中增加一条。用户可以导航到上一个状态。例如,对产品列表排序
"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")}>Sort Ascending</button>
<button onClick={() => updateSorting("desc")}>Sort Descending</button>
</>
);
}
"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>
</>
);
}
window.history.replaceState
使用它来替换浏览器历史栈中的当前项。用户无法导航回上一个状态。例如,切换应用程序的应用设置:
"use client";
import { usePathname } from "next/navigation";
export function LocaleSwitcher() {
const pathname = usePathname();
function switchLocale(locale: string) {
// 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>
</>
);
}
"use client";
import { usePathname } from "next/navigation";
export 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>
</>
);
}
路由和导航的工作原理
App Router 使用混合 方法进行路由和导航.在服务器上, 您的应用程序代码按路由段进行自动代码拆分。在客户端, Next.js prefetches 和 caches 路由段。这意味着当用户导航到一个新路由时,浏览器不会重新加载页面。并且只有更改了的路由段会重新渲染-提升了导航体验和性能。
1. 代码拆分
代码拆分允许您拆分您的应用程序代码为更小的包供浏览器下载和执行。这减少了每个请求的传输数据量和执行时间,从未提升性能。
服务器端组件 让您的应用程序代码由路由段自动进行代码拆分。这意味着只有当前路由所需的代码会加载在当前导航。
2. Prefetching
Prefetching 是在用户访问之前,后台预加载路由的一种方式。
Next.js 中预取路由有两种方式:
<Link>组件: 当路由在用户视口可见时,会自动预取路由。 当页面首次加载或通过滚动进入视图时,预取发生。router.prefetch():useRouterhook 可用于编程方式预取路由。
您可以通过设置prefetch属性未false来禁止预取。或者,您可以通过将prefetch属性设置为true来预取超出加载边界的整个页面数据。
参阅<Link> API reference获取更多信息。
您需要知道:
- 预取在开发中不可用,只能在生产中可用。
3. Caching
Next.js 有一个 内存的客户端缓存 叫做Router Cache。当用户浏览应用程序时,预取的路由段和访问的路由的 React 服务器端组件负载存储在缓存中。
这意味着在导航时, 会尽可能的使用缓存,而不是向服务器发送新请求 - 从而通过减少请求数量和数据传输来提升性能。
了解更多关于Router Cache 工作原理和如何配置。
4. 局部渲染
and any shared segments are preserved. 局部渲染意味着只有在导航时变化了的路由段会在客户端重新渲染,并且会保留共享段。
例如,当在两个同级路由/dashboard/settings 和 /dashboard/analytics间导航时, 会渲染settings 和 analytics 页面,但会保留共同的dashboard 布局。
没有局部渲染, 客户端的每个导航都会导致整个页面重新渲染。只渲染变化了的段减少了数据传输数量和执行时间,从而提升性能。
5. 软导航
页面间导航时浏览器执行"硬导航"。Next.js App Router 能实现页面间"软导航", 确保只有变化了的路由段会重新渲染(部分渲染). 这使得导航期间能够存储客户端 React 状态。
6. 回退和前进导航
默认情况下, Next.js 会维持前进或回退时滚动导航的位置, 并再次使用Router Cache中的路由段。