Linking 和 Navigation

Nextjs 的路由使用服务端中心路由以及客户端导航。它支持即时加载和并发渲染。这就意味着导航中可以维护客户端的状态,避免昂贵的重新渲染。

有两种导航方式:

  • <Link/>组件
  • useRouterHook

Link 组件

Link 是继承自<a>元素的 React 组件并且支持数据预取以及客户端导航。这是主要的路由方式。

从next/link导入,并传递一个href的 prop。

typescript
1import Link from 'next/link'
2
3export default function Page() {
4  return <Link href="/dashboard">Dashboard</Link>
5}

还有其他的可选属性,请查看 API 文档。

例子

导航到动态 segment

当需要链接到动态 segment 时,你可以使用模版文字和插值去生成链接。

typescript
1import Link from 'next/link'
2
3export default function PostList({ posts }) {
4  return (
5    <ul>
6      {posts.map((post) => (
7        <li key={post.id}>
8          <Link href={`/blog/${post.slug}`}>{post.title}</Link>
9        </li>
10      ))}
11    </ul>
12  )
13}

你可以使用usePathname去确定 link 是否处于 active 状态。举个例子:给 active 的 link 添加 class,你可以检测当前的 pathname 是否匹配 link 的 href。

tsx
1'use client'
2
3import { usePathname } from 'next/navigation'
4import Link from 'next/link'
5
6export function Navigation({ navLinks }) {
7  const pathname = usePathname()
8
9  return (
10    <>
11      {navLinks.map((link) => {
12        const isActive = pathname.startsWith(link.href)
13
14        return (
15          <Link className={isActive ? 'text-blue' : 'text-black'} href={link.href} key={link.name}>
16            {link.name}
17          </Link>
18        )
19      })}
20    </>
21  )
22}

滚动到 id

<Link>的默认行为是滚动到已更改的路由 segment 的顶部。当 href 中定义了一个 id 时,它将滚动到特定的 id,类似于普通的<a>标记。

useRouter Hook

useRouter hook 允许你以编程方式手动改变路由,仅限于客户端组件。

在next/navigation中引入,并且在客户端组件中调用。

typescript
1'use client'
2
3import { useRouter } from 'next/navigation'
4
5export default function Page() {
6  const router = useRouter()
7
8  return (
9    <button type="button" onClick={() => router.push('/dashboard')}>
10      Dashboard
11    </button>
12  )
13}

useRouter提供诸如push、refresh等方法。

推荐方式:除非你有特定的需求,否则尽量用 Link 来导航。

  • 路由通过 Link 或者 router.push()切换
  • 路由会改变浏览器的地址栏
  • 路由通过重新使用没有改变的 segment (比如共享 layout)来避免不必要的工作。这也称为部分渲染。这种形式的导航叫 soft navigation(软导航)。
  • 如果满足 soft navigation 的条件,路由器将从缓存而不是服务器中获取新的 segment 数据。如果没有,路由器将执行 hard navigation 并获取服务器组件信息。
  • 从服务器获取数据时,可以显示 loading UI
  • 路由器最终会使用缓存或者新的数据更新客户端的页面

已渲染的服务端组件在客户端缓存

新的路由有个客户端缓存功能,用于存储服务器组件(负载信息)的渲染结果。该缓存按路由的 segment 进行划分,这样的划分方式能允许任一级别的缓存失效,并确保在并发渲染中保持一致性。

当用户在应用程序中导航时,路由器将在缓存中存储先前获取的 segment 和预获取的 segment 的负载信息。

这意味着在某些情况下,路由器可以重用缓存,而无需向服务器发起新请求。这通过避免不必要的重新获取数据和重新渲染组件来提高性能。

使缓存失效

预取缓存

预取是一种在访问路由之前以后台方式预加载路由的方法。预取路由的渲染结果将被添加到路由器的客户端端缓存中。这使得导航到预取的路由上几乎是即时的。

默认情况下,使用<Link>组件时,路由在视口中变得可见时会被预取。这可以在页面首次加载时或通过滚动时发生。还可以通过useRouter()钩子的prefetch方法对路由进行编程预取。

静态路由和动态路由

如果路由是静态的,将预取所有该路由 segment 的服务器组件负载。 如果路由是动态的,则会预取从共享布局(Shared Layout)到第一个 loading.js 文件之间的负载。这降低了动态预取整个路由的成本,并允许动态路由的即时加载状态。

  • 路由预取功能仅在生产环境中启用
  • 通过将prefetch={false}传递给<Link>,可以禁用预取缓存。

Soft Navigation

在导航上,变更过的 segment 缓存(如果存在的话)就会被重新使用,这样就不会产生新的请求。

**什么情况下会满足 Soft Navigation **

在导航时,如果你要导航到的路由已经被预取,并且要么不包含动态 segment,要么与当前路由具有相同的动态参数,Next.js 将使用软导航(soft navigation)。

官方给了一个例子:

一个动态的路由分段[team]:/dashboard/[team]/*。当[team]发生改变时,原来缓存着的数据都会失效,并走 hard navigation。

  • /dashboard/team-red/* ==> /dashboard/team-red/*走 soft navigation
  • /dashboard/team-red/*==> /dashboard/team-blue/*走 hard navigation

hard Navigation

在导航时,缓存被清除,服务器重新获取数据并重新渲染已更改的部分。

回退/前进导航

后退(back)和前进(forward)导航(popstate 事件)具有软导航行为。这意味着客户端缓存被重用,并且导航几乎是即时的。

焦点和滚动管理

默认情况下,Next.js 会将焦点设置并滚动到视图中已更改的部分