Next.js 面试题详细答案 - Q13
Q13: 解释 next/navigation 和 next/link 的用法:如何使用 useRouter, usePathname, useSearchParams 等钩子? 组件相比原生 标签的优势是什么?
next/navigation 钩子使用
1. useRouter - 路由导航
'use client'
import { useRouter } from 'next/navigation'
function NavigationExample() {
const router = useRouter()
const handleNavigation = () => {
// 编程式导航
router.push('/about')
}
const handleReplace = () => {
// 替换当前历史记录
router.replace('/login')
}
const handleBack = () => {
// 返回上一页
router.back()
}
const handleForward = () => {
// 前进到下一页
router.forward()
}
const handleRefresh = () => {
// 刷新当前页面
router.refresh()
}
return (
<div>
<button onClick={handleNavigation}>跳转到关于页面</button>
<button onClick={handleReplace}>替换为登录页面</button>
<button onClick={handleBack}>返回</button>
<button onClick={handleForward}>前进</button>
<button onClick={handleRefresh}>刷新</button>
</div>
)
}
2. usePathname - 获取当前路径
'use client'
import { usePathname } from 'next/navigation'
function Navigation() {
const pathname = usePathname()
return (
<nav>
<ul>
<li className={pathname === '/' ? 'active' : ''}>
<a href="/">首页</a>
</li>
<li className={pathname === '/about' ? 'active' : ''}>
<a href="/about">关于</a>
</li>
<li className={pathname === '/blog' ? 'active' : ''}>
<a href="/blog">博客</a>
</li>
<li className={pathname.startsWith('/blog/') ? 'active' : ''}>
<a href="/blog">博客详情</a>
</li>
</ul>
</nav>
)
}
// 动态导航组件
function DynamicNavigation() {
const pathname = usePathname()
const isActive = (path) => {
if (path === '/') {
return pathname === '/'
}
return pathname.startsWith(path)
}
const navItems = [
{ href: '/', label: '首页' },
{ href: '/about', label: '关于' },
{ href: '/blog', label: '博客' },
{ href: '/contact', label: '联系' },
]
return (
<nav>
<ul>
{navItems.map((item) => (
<li key={item.href} className={isActive(item.href) ? 'active' : ''}>
<a href={item.href}>{item.label}</a>
</li>
))}
</ul>
</nav>
)
}
3. useSearchParams - 获取查询参数
'use client'
import { useSearchParams, useRouter, usePathname } from 'next/navigation'
function SearchFilters() {
const searchParams = useSearchParams()
const router = useRouter()
const pathname = usePathname()
const category = searchParams.get('category')
const sort = searchParams.get('sort')
const page = searchParams.get('page')
const updateFilter = (key, value) => {
const params = new URLSearchParams(searchParams)
if (value) {
params.set(key, value)
} else {
params.delete(key)
}
router.push(`${pathname}?${params.toString()}`)
}
const clearFilters = () => {
router.push(pathname)
}
return (
<div>
<h3>当前筛选条件:</h3>
<p>分类: {category || '全部'}</p>
<p>排序: {sort || '默认'}</p>
<p>页码: {page || '1'}</p>
<div>
<label>
分类:
<select
value={category || ''}
onChange={(e) => updateFilter('category', e.target.value)}
>
<option value="">全部</option>
<option value="tech">技术</option>
<option value="life">生活</option>
<option value="travel">旅行</option>
</select>
</label>
<label>
排序:
<select
value={sort || ''}
onChange={(e) => updateFilter('sort', e.target.value)}
>
<option value="">默认</option>
<option value="newest">最新</option>
<option value="oldest">最旧</option>
<option value="popular">最受欢迎</option>
</select>
</label>
<button onClick={clearFilters}>清除筛选</button>
</div>
</div>
)
}
4. 组合使用钩子
'use client'
import { useRouter, usePathname, useSearchParams } from 'next/navigation'
import { useState, useEffect } from 'react'
function ProductFilters() {
const router = useRouter()
const pathname = usePathname()
const searchParams = useSearchParams()
const [filters, setFilters] = useState({
category: searchParams.get('category') || '',
price: searchParams.get('price') || '',
brand: searchParams.get('brand') || '',
})
useEffect(() => {
const params = new URLSearchParams()
Object.entries(filters).forEach(([key, value]) => {
if (value) {
params.set(key, value)
}
})
const queryString = params.toString()
const newUrl = queryString ? `${pathname}?${queryString}` : pathname
router.push(newUrl)
}, [filters, pathname, router])
const handleFilterChange = (key, value) => {
setFilters(prev => ({
...prev,
[key]: value,
}))
}
return (
<div>
<h3>产品筛选</h3>
<div>
<label>
分类:
<select
value={filters.category}
onChange={(e) => handleFilterChange('category', e.target.value)}
>
<option value="">全部</option>
<option value="electronics">电子产品</option>
<option value="clothing">服装</option>
<option value="books">图书</option>
</select>
</label>
<label>
价格:
<select
value={filters.price}
onChange={(e) => handleFilterChange('price', e.target.value)}
>
<option value="">全部</option>
<option value="0-100">0-100元</option>
<option value="100-500">100-500元</option>
<option value="500-1000">500-1000元</option>
</select>
</label>
<label>
品牌:
<input
type="text"
value={filters.brand}
onChange={(e) => handleFilterChange('brand', e.target.value)}
placeholder="输入品牌名称"
/>
</label>
</div>
</div>
)
}
next/link 组件使用
1. 基本用法
import Link from 'next/link'
function Navigation() {
return (
<nav>
<ul>
<li>
<Link href="/">首页</Link>
</li>
<li>
<Link href="/about">关于我们</Link>
</li>
<li>
<Link href="/blog">博客</Link>
</li>
<li>
<Link href="/contact">联系我们</Link>
</li>
</ul>
</nav>
)
}
// 动态链接
function BlogList({ posts }) {
return (
<ul>
{posts.map((post) => (
<li key={post.id}>
<Link href={`/blog/${post.slug}`}>
{post.title}
</Link>
</li>
))}
</ul>
)
}
2. 链接样式和状态
import Link from 'next/link'
import { usePathname } from 'next/navigation'
function StyledNavigation() {
const pathname = usePathname()
return (
<nav>
<ul>
<li>
<Link
href="/"
className={pathname === '/' ? 'active' : ''}
>
首页
</Link>
</li>
<li>
<Link
href="/about"
className={pathname === '/about' ? 'active' : ''}
>
关于
</Link>
</li>
<li>
<Link
href="/blog"
className={pathname.startsWith('/blog') ? 'active' : ''}
>
博客
</Link>
</li>
</ul>
</nav>
)
}
// 自定义链接组件
function CustomLink({ href, children, className }) {
return (
<Link href={href} className={className}>
{children}
</Link>
)
}
3. 链接预加载
import Link from 'next/link'
function ProductList({ products }) {
return (
<div>
{products.map((product) => (
<div key={product.id}>
<h3>{product.name}</h3>
<p>{product.description}</p>
<Link
href={`/products/${product.id}`}
prefetch={true} // 预加载链接
>
查看详情
</Link>
</div>
))}
</div>
)
}
// 条件预加载
function ConditionalPrefetch({ product }) {
return (
<Link
href={`/products/${product.id}`}
prefetch={product.featured} // 只有精选产品才预加载
>
{product.name}
</Link>
)
}
4. 外部链接处理
import Link from 'next/link'
function ExternalLinks() {
return (
<div>
<h3>内部链接</h3>
<Link href="/about">关于我们</Link>
<h3>外部链接</h3>
<Link
href="https://example.com"
target="_blank"
rel="noopener noreferrer"
>
外部网站
</Link>
<h3>邮件链接</h3>
<Link href="mailto:contact@example.com">
联系我们
</Link>
<h3>电话链接</h3>
<Link href="tel:+1234567890">
拨打电话
</Link>
</div>
)
}
Link 组件 vs 原生 标签
1. 性能优势
// 使用 Link 组件 - 客户端路由
import Link from 'next/link'
function GoodNavigation() {
return (
<nav>
<Link href="/about">关于我们</Link>
<Link href="/blog">博客</Link>
</nav>
)
}
// 使用原生 <a> 标签 - 完整页面刷新
function BadNavigation() {
return (
<nav>
<a href="/about">关于我们</a>
<a href="/blog">博客</a>
</nav>
)
}
2. 预加载功能
import Link from 'next/link'
function PrefetchExample() {
return (
<div>
{/* Link 组件自动预加载可见链接 */}
<Link href="/about">关于我们</Link>
{/* 可以控制预加载行为 */}
<Link href="/blog" prefetch={false}>
博客(不预加载)
</Link>
{/* 原生 <a> 标签没有预加载功能 */}
<a href="/contact">联系我们(无预加载)</a>
</div>
)
}
3. 路由状态管理
'use client'
import Link from 'next/link'
import { useRouter } from 'next/navigation'
function NavigationWithState() {
const router = useRouter()
const handleLinkClick = (href) => {
// Link 组件自动处理路由状态
console.log('导航到:', href)
}
return (
<nav>
<Link href="/about" onClick={() => handleLinkClick('/about')}>
关于我们
</Link>
{/* 原生 <a> 标签需要手动处理 */}
<a
href="/blog"
onClick={(e) => {
e.preventDefault()
router.push('/blog')
handleLinkClick('/blog')
}}
>
博客
</a>
</nav>
)
}
实际应用示例
1. 面包屑导航
'use client'
import Link from 'next/link'
import { usePathname } from 'next/navigation'
function Breadcrumb() {
const pathname = usePathname()
const pathSegments = pathname.split('/').filter(Boolean)
return (
<nav aria-label="面包屑导航">
<ol>
<li>
<Link href="/">首页</Link>
</li>
{pathSegments.map((segment, index) => {
const href = '/' + pathSegments.slice(0, index + 1).join('/')
const isLast = index === pathSegments.length - 1
return (
<li key={href}>
{isLast ? (
<span>{segment}</span>
) : (
<Link href={href}>{segment}</Link>
)}
</li>
)
})}
</ol>
</nav>
)
}
2. 分页组件
'use client'
import Link from 'next/link'
import { useSearchParams } from 'next/navigation'
function Pagination({ currentPage, totalPages }) {
const searchParams = useSearchParams()
const createPageUrl = (page) => {
const params = new URLSearchParams(searchParams)
params.set('page', page.toString())
return `?${params.toString()}`
}
return (
<nav>
<ul>
{currentPage > 1 && (
<li>
<Link href={createPageUrl(currentPage - 1)}>
上一页
</Link>
</li>
)}
{Array.from({ length: totalPages }, (_, i) => i + 1).map((page) => (
<li key={page}>
<Link
href={createPageUrl(page)}
className={page === currentPage ? 'active' : ''}
>
{page}
</Link>
</li>
))}
{currentPage < totalPages && (
<li>
<Link href={createPageUrl(currentPage + 1)}>
下一页
</Link>
</li>
)}
</ul>
</nav>
)
}
总结
next/navigation 钩子优势:
- useRouter:提供编程式导航控制
- usePathname:获取当前路径,用于条件渲染
- useSearchParams:获取和操作查询参数
- 性能:Link 使用客户端路由,避免完整页面刷新
- 预加载:Link 自动预加载可见链接,提高用户体验
- 状态管理:Link 自动处理路由状态和浏览器历史
- SEO:Link 在服务端渲染时生成正确的 标签
- 用户体验:更快的导航和更流畅的页面切换
这些工具让 Next.js 应用能够提供更好的导航体验和性能。