WebNavigation(React) web-navigation

这份指南提供了React web应用程序的导航和路由模式,包括React Router和Next.js路由的实现,深度链接处理,以及导航守卫等高级功能。

前端开发 0 次安装 0 次浏览 更新于 3/3/2026

name: web-navigation description: 用于React web应用程序的导航和路由模式。在实现React Router、Next.js路由、深度链接或处理导航状态时使用。

Web Navigation (React)

React Router (v6)

基本设置

// App.tsx
import { BrowserRouter, Routes, Route } from 'react-router-dom';

function App() {
  return (
    <BrowserRouter>
      <Routes>
        <Route path="/" element={<HomePage />} />
        <Route path="/about" element={<AboutPage />} />
        <Route path="/users" element={<UsersPage />} />
        <Route path="/users/:id" element={<UserDetailPage />} />
        <Route path="*" element={<NotFoundPage />} />
      </Routes>
    </BrowserRouter>
  );
}

嵌套路由和布局

// 带有共享UI的布局
function DashboardLayout() {
  return (
    <div className="dashboard">
      <Sidebar />
      <main>
        <Outlet /> {/* 子路由在这里渲染 */}
      </main>
    </div>
  );
}

// 路由
<Routes>
  <Route path="/dashboard" element={<DashboardLayout />}>
    <Route index element={<DashboardHome />} />
    <Route path="analytics" element={<Analytics />} />
    <Route path="settings" element={<Settings />} />
  </Route>
</Routes>

动态路由

import { useParams, useSearchParams } from 'react-router-dom';

// 路由:/users/:id
function UserDetailPage() {
  const { id } = useParams<{ id: string }>();
  const [searchParams, setSearchParams] = useSearchParams();

  const tab = searchParams.get('tab') || 'profile';

  return (
    <div>
      <h1>用户 {id}</h1>
      <TabBar
        active={tab}
        onChange={(t) => setSearchParams({ tab: t })}
      />
    </div>
  );
}

程序化导航

import { useNavigate, useLocation } from 'react-router-dom';

function LoginPage() {
  const navigate = useNavigate();
  const location = useLocation();

  async function handleLogin() {
    await login(credentials);

    // 重定向到预期页面或默认
    const from = location.state?.from?.pathname || '/dashboard';
    navigate(from, { replace: true });
  }

  // 其他导航方法
  navigate('/users');           // 推送到历史记录
  navigate('/users', { replace: true }); // 替换当前条目
  navigate(-1);                 // 返回
  navigate(1);                  // 前进
}

链接组件

import { Link, NavLink } from 'react-router-dom';

// 基本链接
<Link to="/about">关于</Link>

// 带有状态
<Link to="/checkout" state={{ cartId: '123' }}>
  结账
</Link>

// NavLink - 活动样式
<NavLink
  to="/dashboard"
  className={({ isActive }) =>
    isActive ? 'nav-link active' : 'nav-link'
  }
>
  仪表盘
</NavLink>

Next.js App Router

文件基础路由

app/
├── layout.tsx          # 根布局
├── page.tsx            # / 路由
├── about/
│   └── page.tsx        # /about 路由
├── users/
│   ├── page.tsx        # /users 路由
│   └── [id]/
│       └── page.tsx    # /users/:id 路由
├── (auth)/             # 路由组(无URL段)
│   ├── login/
│   │   └── page.tsx    # /login 路由
│   └── register/
│       └── page.tsx    # /register 路由
└── dashboard/
    ├── layout.tsx      # 仪表盘布局
    ├── page.tsx        # /dashboard
    └── settings/
        └── page.tsx    # /dashboard/settings

布局

// app/layout.tsx - 根布局
export default function RootLayout({
  children,
}: {
  children: React.ReactNode;
}) {
  return (
    <html lang="en">
      <body>
        <Providers>
          <Header />
          {children}
          <Footer />
        </Providers>
      </body>
    </html>
  );
}

// app/dashboard/layout.tsx - 嵌套布局
export default function DashboardLayout({
  children,
}: {
  children: React.ReactNode;
}) {
  return (
    <div className="dashboard">
      <Sidebar />
      <main>{children}</main>
    </div>
  );
}

动态路由

// app/users/[id]/page.tsx
interface Props {
  params: { id: string };
  searchParams: { tab?: string };
}

export default function UserPage({ params, searchParams }: Props) {
  const { id } = params;
  const tab = searchParams.tab || 'profile';

  return (
    <div>
      <h1>用户 {id}</h1>
      <Tabs active={tab} />
    </div>
  );
}

// 生成静态参数(可选)
export async function generateStaticParams() {
  const users = await getUsers();
  return users.map((user) => ({
    id: user.id,
  }));
}

程序化导航

'use client';

import { useRouter, usePathname, useSearchParams } from 'next/navigation';

function SearchForm() {
  const router = useRouter();
  const pathname = usePathname();
  const searchParams = useSearchParams();

  function handleSearch(query: string) {
    const params = new URLSearchParams(searchParams);
    params.set('q', query);
    router.push(`${pathname}?${params.toString()}`);
  }

  // 导航方法
  router.push('/dashboard');      // 导航
  router.replace('/dashboard');   // 替换无历史记录
  router.back();                  // 返回
  router.forward();               // 前进
  router.refresh();               // 刷新服务器组件
}

链接组件

import Link from 'next/link';

// 基本链接
<Link href="/about">关于</Link>

// 带有动态路由
<Link href={`/users/${user.id}`}>
  {user.name}
</Link>

// 带有查询参数
<Link href={{ pathname: '/search', query: { q: 'react' } }}>
  搜索
</Link>

// 预加载(默认:true)
<Link href="/dashboard" prefetch={false}>
  仪表盘
</Link>

路由组和组织

受保护的vs公共路由

// React Router
<Routes>
  {/* 公共路由 */}
  <Route path="/login" element={<LoginPage />} />
  <Route path="/register" element={<RegisterPage />} />

  {/* 受保护的路由 */}
  <Route element={<RequireAuth />}>
    <Route path="/dashboard" element={<DashboardPage />} />
    <Route path="/settings" element={<SettingsPage />} />
  </Route>
</Routes>

// Next.js - 使用路由组
// app/(public)/login/page.tsx
// app/(protected)/dashboard/page.tsx
// app/(protected)/layout.tsx - 添加身份验证检查

加载和错误状态

React Router

import { Suspense } from 'react';
import { Await, useLoaderData, defer } from 'react-router-dom';

// 加载器
export async function loader({ params }) {
  return defer({
    user: getUser(params.id), // 承诺
  });
}

// 组件
function UserPage() {
  const { user } = useLoaderData();

  return (
    <Suspense fallback={<Spinner />}>
      <Await resolve={user} errorElement={<ErrorFallback />}>
        {(resolvedUser) => <UserProfile user={resolvedUser} />}
      </Await>
    </Suspense>
  );
}

Next.js

// app/users/[id]/loading.tsx
export default function Loading() {
  return <Spinner />;
}

// app/users/[id]/error.tsx
'use client';

export default function Error({
  error,
  reset,
}: {
  error: Error;
  reset: () => void;
}) {
  return (
    <div>
      <h2>出了点问题!</h2>
      <button onClick={reset}>再试一次</button>
    </div>
  );
}

// app/users/[id]/not-found.tsx
export default function NotFound() {
  return <div>用户未找到</div>;
}

滚动恢复

React Router

import { ScrollRestoration } from 'react-router-dom';

function App() {
  return (
    <BrowserRouter>
      <Routes>{/* ... */}</Routes>
      <ScrollRestoration />
    </BrowserRouter>
  );
}

手动滚动到顶部

import { useEffect } from 'react';
import { useLocation } from 'react-router-dom';

function ScrollToTop() {
  const { pathname } = useLocation();

  useEffect(() => {
    window.scrollTo(0, 0);
  }, [pathname]);

  return null;
}

深度链接/查询参数

// 自定义钩子用于类型安全的查询参数
import { useSearchParams } from 'react-router-dom';

interface Filters {
  category?: string;
  sort?: 'asc' | 'desc';
  page?: number;
}

function useFilters() {
  const [searchParams, setSearchParams] = useSearchParams();

  const filters: Filters = {
    category: searchParams.get('category') || undefined,
    sort: (searchParams.get('sort') as 'asc' | 'desc') || undefined,
    page: Number(searchParams.get('page')) || 1,
  };

  function setFilters(newFilters: Partial<Filters>) {
    const params = new URLSearchParams(searchParams);

    Object.entries(newFilters).forEach(([key, value]) => {
      if (value !== undefined && value !== null) {
        params.set(key, String(value));
      } else {
        params.delete(key);
      }
    });

    setSearchParams(params);
  }

  return { filters, setFilters };
}

// 使用
function ProductList() {
  const { filters, setFilters } = useFilters();

  return (
    <div>
      <CategorySelect
        value={filters.category}
        onChange={(cat) => setFilters({ category: cat })}
      />
      <ProductGrid products={products} />
      <Pagination
        page={filters.page}
        onChange={(p) => setFilters({ page: p })}
      />
    </div>
  );
}

导航守卫

// 防止带有未保存更改的导航
import { useBlocker } from 'react-router-dom';

function EditForm() {
  const [isDirty, setIsDirty] = useState(false);

  const blocker = useBlocker(
    ({ currentLocation, nextLocation }) =>
      isDirty && currentLocation.pathname !== nextLocation.pathname
  );

  return (
    <>
      <form onChange={() => setIsDirty(true)}>
        {/* 表单字段 */}
      </form>

      {blocker.state === 'blocked' && (
        <ConfirmDialog
          message="您有未保存的更改。仍然要离开吗?"
          onConfirm={() => blocker.proceed()}
          onCancel={() => blocker.reset()}
        />
      )}
    </>
  );
}

常见模式

操作后的重定向

// 表单提交后
async function handleSubmit(data: FormData) {
  const result = await createItem(data);
  navigate(`/items/${result.id}`);
}

// 登录后
async function handleLogin() {
  await login(credentials);
  const redirectTo = searchParams.get('redirect') || '/dashboard';
  navigate(redirectTo, { replace: true });
}

带有URL的标签导航

function UserProfile() {
  const [searchParams, setSearchParams] = useSearchParams();
  const tab = searchParams.get('tab') || 'overview';

  const tabs = ['overview', 'activity', 'settings'];

  return (
    <div>
      <nav>
        {tabs.map((t) => (
          <button
            key={t}
            onClick={() => setSearchParams({ tab: t })}
            className={tab === t ? 'active' : ''}
          >
            {t}
          </button>
        ))}
      </nav>
      <TabContent tab={tab} />
    </div>
  );
}

常见问题

问题 解决方案
路由不匹配 检查路由顺序(特定路由在动态路由之前)
后退按钮不起作用 使用 navigate() 而不是 window.location
刷新时状态丢失 将状态存储在URL参数中,而不仅仅是状态
滚动位置错误 添加 ScrollRestoration 组件
生产中的404(SPA) 为SPA配置服务器