Linking 和 Navigation
Nextjs 的路由使用服务端中心路由以及客户端导航。它支持即时加载和并发渲染。这就意味着导航中可以维护客户端的状态,避免昂贵的重新渲染。
有两种导航方式:
<Link/>组件useRouterHook
Link 组件
Link 是继承自<a>元素的 React 组件并且支持数据预取以及客户端导航。这是主要的路由方式。
从next/link导入,并传递一个href的 prop。
typescript1import Link from 'next/link' 2 3export default function Page() { 4 return <Link href="/dashboard">Dashboard</Link> 5}
还有其他的可选属性,请查看 API 文档。
例子
导航到动态 segment
当需要链接到动态 segment 时,你可以使用模版文字和插值去生成链接。
typescript1import 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}
检测活跃的 Link
你可以使用usePathname去确定 link 是否处于 active 状态。举个例子:给 active 的 link 添加 class,你可以检测当前的 pathname 是否匹配 link 的 href。
tsx1'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中引入,并且在客户端组件中调用。
typescript1'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 来导航。
Navigation 是如何工作的
- 路由通过
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 会将焦点设置并滚动到视图中已更改的部分