一、 路由与导航 (Routing & Navigation)
这是最常用的一类 Hooks,它们让你能够与 Next.js 强大的路由系统进行交互。
1. useRouter
(适用于 Pages Router 和 App Router)
useRouter
是最经典、功能最全面的路由 Hook。它返回一个路由器(router)对象,包含了当前路由的各种信息和进行导航的方法。
何时使用:
- 需要进行编程式导航(即通过代码跳转页面,而不是通过
<Link>
组件)。 - 需要同时访问路径、查询参数以及导航方法。
返回值 (部分核心属性):
router.push(path)
: 客户端导航到新页面,并将新页面添加到浏览器历史记录中。router.replace(path)
: 客户端导航到新页面,但替换当前的历史记录项。router.back()
: 返回到历史记录中的上一页。router.prefetch(path)
: 在后台预取页面资源,加快后续导航速度。router.query
: 一个对象,包含了动态路由参数和查询字符串参数(在 Pages Router 中非常常用)。router.pathname
: 当前页面的路径名(不含查询参数)。router.isReady
: 一个布尔值,在 Pages Router 中,表示路由器字段(如query
)是否已在客户端更新并准备就绪。
示例 (App Router):
// app/components/LoginButton.js
'use client'; // 必须是客户端组件
import { useRouter } from 'next/navigation'; // 注意导入来源是 next/navigation
export default function LoginButton() {
const router = useRouter();
const handleLogin = () => {
// 假设登录成功
console.log('Login successful, redirecting to dashboard...');
router.push('/dashboard');
};
return <button onClick={handleLogin}>Login and Go to Dashboard</button>;
}
注意: 在 App Router 中,useRouter
的导入来源是 next/navigation
,而在 Pages Router 中是 next/router
。App Router 中的 useRouter
返回值更精简,不包含 pathname
, query
等属性,因为这些被新的、更细粒度的 Hooks 替代了。
2. usePathname
(仅限 App Router)
这是一个只读的 Hook,用于获取当前页面的路径名 (pathname)。
何时使用:
- 当你只需要知道当前的 URL 路径,例如用于高亮导航栏中的活动链接。
返回值:
- 一个字符串,如
/dashboard/settings
。
示例:
// app/components/ActiveLink.js
'use client';
import Link from 'next/link';
import { usePathname } from 'next/navigation';
export default function ActiveLink({ href, children }) {
const pathname = usePathname();
const isActive = pathname === href;
return (
<Link href={href} style={{ color: isActive ? 'blue' : 'black' }}>
{children}
</Link>
);
}
优点: usePathname
比 useRouter
更轻量。当只有路径变化时,仅使用此 Hook 的组件会重新渲染,而不会因为查询参数的变化而重新渲染,从而实现更优的性能。
3. useSearchParams
(仅限 App Router)
这是一个只读的 Hook,用于获取当前 URL 的查询字符串参数 (search params)。
何时使用:
- 需要根据 URL 中的查询参数(如
?q=search&page=2
)来渲染不同内容或执行逻辑。
返回值:
- 一个只读的
URLSearchParams
对象实例。你需要使用.get()
,.getAll()
,.has()
等方法来读取参数。
示例:
// app/search/page.js
'use client';
import { useSearchParams } from 'next/navigation';
import { Suspense } from 'react';
function SearchResults() {
const searchParams = useSearchParams();
const query = searchParams.get('q');
const sortBy = searchParams.get('sortBy') || 'relevance';
// 基于 query 和 sortBy 来获取和显示数据...
return (
<div>
<h1>Showing results for: "{query}"</h1>
<p>Sorted by: {sortBy}</p>
</div>
);
}
// 建议将使用 useSearchParams 的组件包裹在 Suspense 中
// 因为它会在页面首次渲染时等待参数可用,可能会触发 Suspense Fallback
export default function SearchPage() {
return (
<Suspense fallback={<div>Loading...</div>}>
<SearchResults />
</Suspense>
)
}
4. useParams
(仅限 App Router)
这是一个只读的 Hook,用于获取当前路由的动态路由参数。
何时使用:
- 在动态路由页面(如
app/blog/[slug]/page.js
)中,获取动态段的值(如slug
)。
返回值:
- 一个对象,键是动态路由段的名称,值是其具体内容。
示例 (app/products/[id]/page.js
):
// app/products/[id]/components/ProductDetails.js
'use client';
import { useParams } from 'next/navigation';
import { useEffect, useState } from 'react';
export default function ProductDetails() {
const params = useParams(); // params 会是 { id: '...' }
const [product, setProduct] = useState(null);
useEffect(() => {
// 在客户端根据 id 获取商品数据
fetch(`/api/products/${params.id}`)
.then((res) => res.json())
.then(setProduct);
}, [params.id]);
if (!product) return <div>Loading product...</div>;
return <h1>{product.name}</h1>;
}
最佳实践提醒: 虽然上面的例子可以在客户端获取数据,但在 App Router 中,更推荐的做法是在服务端组件 (page.js
) 中通过 params
props 接收动态参数并获取数据,然后将数据作为 props 传递给客户端组件。这样可以利用服务器端渲染 (SSR) 或静态生成 (SSG) 的优势。
// app/products/[id]/page.js (服务端组件)
async function getProduct(id) {
const res = await fetch(`https://api.example.com/products/${id}`);
return res.json();
}
export default async function ProductPage({ params }) {
// params 直接从 props 中获取,无需 hook
const product = await getProduct(params.id);
// 可以将数据传递给客户端组件
return <ProductDetails initialProduct={product} />;
}
二、元数据 (Metadata)
在 App Router 中,元数据(如页面标题 <title>
、描述 <meta name="description">
)主要通过 metadata
或 generateMetadata
导出对象来管理。但对于需要在客户端动态修改元数据的场景,可以使用 use-metadata
这样的社区库或手动操作 DOM,Next.js 本身没有提供一个官方的 useMetadata
Hook。
三、其他特殊用途的 Hooks
useSelectedLayoutSegment
/ useSelectedLayoutSegments
(仅限 App Router)
这些是更高级的 Hooks,通常用于复杂的布局,如并排路由 (Parallel Routes) 或拦截路由 (Intercepting Routes)。
useSelectedLayoutSegment(parallelRoutesKey)
: 读取由特定layout
下的page.js
渲染的路由段。useSelectedLayoutSegments()
: 返回一个数组,包含从根布局到当前页面的所有活动路由段。
何时使用:
- 当你在一个共享的布局(如
layout.js
)中,需要知道哪个子页面或子路由当前是活动的,以便构建动态的 UI(例如,一个始终显示活动选项卡的选项卡栏)。
示例 (app/dashboard/layout.js
)
'use client';
import { useSelectedLayoutSegment } from 'next/navigation';
import Link from 'next/link';
export default function DashboardLayout({ children }) {
// `segment` 的值会是 'settings', 'profile', 或 null (当在 /dashboard 时)
const segment = useSelectedLayoutSegment();
return (
<div>
<nav>
<Link href="/dashboard/settings" style={{ textDecoration: segment === 'settings' ? 'underline' : 'none' }}>
Settings
</Link>
<Link href="/dashboard/profile" style={{ textDecoration: segment === 'profile' ? 'underline' : 'none' }}>
Profile
</Link>
</nav>
{children}
</div>
);
}
总结与对比
Hook | 主要用途 | 适用范围 | 返回值类型 | 备注 |
---|---|---|---|---|
useRouter | 编程式导航、访问完整路由信息 | Pages & App Router | Router 对象 | App Router 版更精简,专注于导航。 |
usePathname | (只读) 获取当前 URL 路径名 | App Router | string | 性能更优,只关心路径。 |
useSearchParams | (只读) 获取 URL 查询参数 | App Router | URLSearchParams | 性能更优,只关心查询参数。建议与 Suspense 配合。 |
useParams | (只读) 获取动态路由参数 | App Router | object | 客户端获取动态路由值的标准方式。 |
useSelectedLayoutSegment(s) | (只读) 在布局中确定活动的子路由 | App Router | string / string[] | 用于构建复杂的、感知路由的布局。 |
核心要点:
- 客户端专属: 上述所有 Next.js Hooks 都必须在标记为
'use client'
的客户端组件中使用。它们依赖于浏览器环境和 React 的状态管理。 - 细粒度原则 (App Router): App Router 鼓励使用更细粒度的 Hooks (
usePathname
,useSearchParams
,useParams
),而不是一把抓的useRouter
。这使得组件能够更精确地响应它们所依赖的路由部分的变化,从而优化渲染性能。 - 数据获取: Next.js 没有一个官方的
useData
或useFetch
Hook。数据获取的模式是:- 首选: 在服务端组件中通过
async/await
fetch
获取数据,通过 props 传递。 - 客户端: 当需要在客户端(如用户交互后)获取数据时,官方推荐使用 SWR (
useSWR
) 或其他数据获取库(如 TanStack Query)。useSWR
由 Vercel 团队开发,与 Next.js 配合得天衣无缝。
- 首选: 在服务端组件中通过
希望这个详细的解释能帮助你更好地理解和使用 Next.js 中的 Hooks!