Q13: 解释 next/navigation 和 next/link 的用法:如何使用 useRouter, usePathname, useSearchPa

47 阅读2分钟

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 钩子优势

  1. useRouter:提供编程式导航控制
  2. usePathname:获取当前路径,用于条件渲染
  3. useSearchParams:获取和操作查询参数

Link 组件 vs 原生 标签

  1. 性能:Link 使用客户端路由,避免完整页面刷新
  2. 预加载:Link 自动预加载可见链接,提高用户体验
  3. 状态管理:Link 自动处理路由状态和浏览器历史
  4. SEO:Link 在服务端渲染时生成正确的 标签
  5. 用户体验:更快的导航和更流畅的页面切换

这些工具让 Next.js 应用能够提供更好的导航体验和性能。