Skip to content

React Router 路由导航

React Router 是 React 最流行的路由库,用于构建单页应用(SPA)的导航系统。

警告

本教程基于 React Router v6,官方文档

安装

bash
npm install react-router-dom

# 或使用 pnpm
pnpm add react-router-dom

# 或使用 yarn
yarn add react-router-dom

基础使用

1. 配置路由

jsx
import {BrowserRouter, Routes, Route} from "react-router-dom";
import Home from "./pages/Home";
import About from "./pages/About";
import Contact from "./pages/Contact";

function App() {
    return (
        <BrowserRouter>
            <Routes>
                <Route path="/" element={<Home />} />
                <Route path="/about" element={<About />} />
                <Route path="/contact" element={<Contact />} />
            </Routes>
        </BrowserRouter>
    );
}

export default App;

2. 导航链接

使用 Link 组件进行导航:

jsx
import {Link} from "react-router-dom";

function Navigation() {
    return (
        <nav>
            <Link to="/">首页</Link>
            <Link to="/about">关于</Link>
            <Link to="/contact">联系</Link>
        </nav>
    );
}

export default Navigation;

3. 编程式导航

使用 useNavigate Hook 进行编程式导航:

jsx
import {useNavigate} from "react-router-dom";

function Home() {
    const navigate = useNavigate();

    const handleClick = () => {
        navigate('/about');
        // 或 navigate('/about', { replace: true });
    };

    return (
        <div>
            <h1>首页</h1>
            <button onClick={handleClick}>跳转到关于页面</button>
        </div>
    );
}

export default Home;

路由参数

1. URL 参数(useParams)

jsx
import {Routes, Route, useParams} from "react-router-dom";

// 定义带参数的路由
function App() {
    return (
        <Routes>
            <Route path="/user/:id" element={<UserProfile />} />
            <Route path="/post/:category/:slug" element={<PostDetail />} />
        </Routes>
    );
}

// 获取 URL 参数
function UserProfile() {
    const {id} = useParams();
    return <div>用户 ID: {id}</div>;
}

function PostDetail() {
    const {category, slug} = useParams();
    return (
        <div>
            <p>分类: {category}</p>
            <p>标题: {slug}</p>
        </div>
    );
}

2. 查询参数(useSearchParams)

jsx
import {useSearchParams} from "react-router-dom";

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

    // 获取查询参数
    const keyword = searchParams.get('keyword');
    const page = searchParams.get('page') || '1';

    const handleSearch = (newKeyword) => {
        // 更新查询参数
        setSearchParams({keyword: newKeyword, page: '1'});
    };

    return (
        <div>
            <input 
                value={keyword || ''} 
                onChange={(e) => handleSearch(e.target.value)} 
            />
            <p>当前页码: {page}</p>
        </div>
    );
}

嵌套路由

1. 使用 Outlet

jsx
import {Routes, Route, Outlet, Link} from "react-router-dom";

function Layout() {
    return (
        <div>
            <nav>
                <Link to="/dashboard">仪表盘</Link>
                <Link to="/dashboard/profile">个人资料</Link>
                <Link to="/dashboard/settings">设置</Link>
            </nav>
            <Outlet /> {/* 子路由会在这里渲染 */}
        </div>
    );
}

function App() {
    return (
        <Routes>
            <Route path="/dashboard" element={<Layout />}>
                <Route index element={<Dashboard />} />
                <Route path="profile" element={<Profile />} />
                <Route path="settings" element={<Settings />} />
            </Route>
        </Routes>
    );
}

2. 嵌套路由配置示例

jsx
function Dashboard() {
    return <div>仪表盘首页</div>;
}

function Profile() {
    return <div>个人资料页面</div>;
}

function Settings() {
    return <div>设置页面</div>;
}

路由守卫

1. 受保护的路由

jsx
import {Navigate, Outlet} from "react-router-dom";

function ProtectedRoute({isAuthenticated}) {
    if (!isAuthenticated) {
        return <Navigate to="/login" replace />;
    }
    return <Outlet />;
}

function App() {
    const isAuthenticated = true; // 从状态管理或 Context 获取

    return (
        <Routes>
            <Route path="/login" element={<Login />} />
            <Route element={<ProtectedRoute isAuthenticated={isAuthenticated} />}>
                <Route path="/dashboard" element={<Dashboard />} />
                <Route path="/profile" element={<Profile />} />
            </Route>
        </Routes>
    );
}

2. 重定向

jsx
import {Navigate} from "react-router-dom";

function App() {
    return (
        <Routes>
            <Route path="/" element={<Navigate to="/home" replace />} />
            <Route path="/home" element={<Home />} />
        </Routes>
    );
}

路由匹配

1. 精确匹配

jsx
<Routes>
    <Route path="/" element={<Home />} />
    <Route path="/about" element={<About />} />
    <Route path="*" element={<NotFound />} /> {/* 404 页面 */}
</Routes>

2. 通配符路由

jsx
<Routes>
    <Route path="/blog/*" element={<BlogLayout />}>
        <Route path=":slug" element={<BlogPost />} />
    </Route>
</Routes>

路由钩子

1. useLocation

获取当前路由位置信息:

jsx
import {useLocation} from "react-router-dom";

function CurrentLocation() {
    const location = useLocation();
    
    return (
        <div>
            <p>路径: {location.pathname}</p>
            <p>查询: {location.search}</p>
            <p>哈希: {location.hash}</p>
            <p>状态: {JSON.stringify(location.state)}</p>
        </div>
    );
}

2. useNavigate

编程式导航:

jsx
import {useNavigate} from "react-router-dom";

function NavigationExample() {
    const navigate = useNavigate();

    return (
        <div>
            <button onClick={() => navigate('/about')}>前进</button>
            <button onClick={() => navigate(-1)}>后退</button>
            <button onClick={() => navigate(1)}>前进</button>
            <button onClick={() => navigate('/home', {replace: true})}>
                替换当前历史记录
            </button>
            <button onClick={() => navigate('/user/123', {state: {from: 'home'}})}>
                带状态导航
            </button>
        </div>
    );
}

3. useNavigationType

获取导航类型:

jsx
import {useNavigationType} from "react-router-dom";

function NavigationInfo() {
    const type = useNavigationType(); // POP, PUSH, REPLACE

    return <div>导航类型: {type}</div>;
}

NavLink 可以在当前路由匹配时添加样式:

jsx
import {NavLink} from "react-router-dom";

function Navigation() {
    return (
        <nav>
            <NavLink 
                to="/" 
                className={({isActive}) => isActive ? 'active' : ''}
            >
                首页
            </NavLink>
            <NavLink 
                to="/about"
                style={({isActive}) => ({
                    color: isActive ? 'red' : 'black'
                })}
            >
                关于
            </NavLink>
        </nav>
    );
}

懒加载路由

jsx
import {lazy, Suspense} from "react";
import {Routes, Route} from "react-router-dom";

const Home = lazy(() => import('./pages/Home'));
const About = lazy(() => import('./pages/About'));

function App() {
    return (
        <Suspense fallback={<div>加载中...</div>}>
            <Routes>
                <Route path="/" element={<Home />} />
                <Route path="/about" element={<About />} />
            </Routes>
        </Suspense>
    );
}

路由配置对象

可以使用配置对象的方式定义路由:

jsx
import {useRoutes} from "react-router-dom";

const routeConfig = [
    {
        path: '/',
        element: <Layout />,
        children: [
            {index: true, element: <Home />},
            {path: 'about', element: <About />},
            {path: 'contact', element: <Contact />},
        ]
    },
    {
        path: '*',
        element: <NotFound />
    }
];

function App() {
    const routes = useRoutes(routeConfig);
    return routes;
}

常见场景

1. 登录后跳转

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

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

    const handleLogin = () => {
        // 登录逻辑...
        
        // 跳转到登录前的页面,或默认跳转到首页
        const from = location.state?.from?.pathname || '/dashboard';
        navigate(from, {replace: true});
    };

    return <button onClick={handleLogin}>登录</button>;
}

2. 路由动画

jsx
import {Routes, Route, useLocation} from "react-router-dom";
import {AnimatePresence, motion} from "framer-motion";

function App() {
    const location = useLocation();

    return (
        <AnimatePresence mode="wait">
            <Routes location={location} key={location.pathname}>
                <Route path="/" element={<Home />} />
                <Route path="/about" element={<About />} />
            </Routes>
        </AnimatePresence>
    );
}

3. 面包屑导航

jsx
import {useMatches} from "react-router-dom";

function Breadcrumbs() {
    const matches = useMatches();

    return (
        <nav>
            {matches.map((match, index) => (
                <span key={match.pathname}>
                    {index > 0 && ' / '}
                    {match.pathname || '首页'}
                </span>
            ))}
        </nav>
    );
}

最佳实践

  1. 使用 BrowserRouter 而非 HashRouter(SEO 友好)
  2. 合理使用嵌套路由,避免路由结构过深
  3. 路由懒加载提升首屏加载速度
  4. 统一路由守卫,集中管理权限控制
  5. 使用 TypeScript增强路由类型安全

Router 类型

BrowserRouter vs HashRouter

jsx
import {BrowserRouter, HashRouter} from "react-router-dom";

// BrowserRouter: 使用 HTML5 History API
// URL: http://example.com/about
<BrowserRouter>
    <App />
</BrowserRouter>

// HashRouter: 使用 URL hash
// URL: http://example.com/#/about
<HashRouter>
    <App />
</HashRouter>

最后更新:2025年

这是我的个人文档