React Router是一个声明式的路由库,它允许你在React应用中定义不同的"页面",并在它们之间导航而无需重新加载页面。
安装React Router包
定义路由和组件映射
使用Link组件导航
根据URL渲染对应组件
# React Router v6(当前最新版本)
npm install react-router-dom@6
# 或者使用yarn
yarn add react-router-dom@6
# 安装类型定义(如果使用TypeScript)
npm install --save-dev @types/react-router-dom
// App.js - 主应用组件
import React from 'react';
import { BrowserRouter, Routes, Route, Link } from 'react-router-dom';
// 页面组件
function Home() {
return <h2>首页</h2>;
}
function About() {
return <h2>关于我们</h2>;
}
function Contact() {
return <h2>联系我们</h2>;
}
function NotFound() {
return <h2>404 - 页面未找到</h2>;
}
function App() {
return (
<BrowserRouter>
<div>
<nav>
<ul style={{ display: 'flex', gap: '20px', listStyle: 'none' }}>
<li>
<Link to="/">首页</Link>
</li>
<li>
<Link to="/about">关于</Link>
</li>
<li>
<Link to="/contact">联系</Link>
</li>
</ul>
</nav>
<Routes>
<Route path="/" element={<Home />} />
<Route path="/about" element={<About />} />
<Route path="/contact" element={<Contact />} />
<Route path="*" element={<NotFound />} />
</Routes>
</div>
</BrowserRouter>
);
}
export default App;
// Dashboard.js - 父级路由组件
import React from 'react';
import { Routes, Route, Link, Outlet } from 'react-router-dom';
function Dashboard() {
return (
<div>
<h2>仪表板</h2>
<nav>
<Link to="profile">个人资料</Link>
<Link to="settings">设置</Link>
</nav>
{/* Outlet用于渲染子路由 */}
<Outlet />
</div>
);
}
function Profile() {
return <h3>个人资料页面</h3>;
}
function Settings() {
return <h3>设置页面</h3>;
}
// App.js中的路由配置
function App() {
return (
<BrowserRouter>
<Routes>
<Route path="/" element={<Home />} />
<Route path="/dashboard" element={<Dashboard />} >
{/* 嵌套路由 */}
<Route path="profile" element={<Profile />} />
<Route path="settings" element={<Settings />} />
{/* 默认子路由 */}
<Route index element={<DashboardHome />} />
</Route>
</Routes>
</BrowserRouter>
);
}
// 产品详情页面
function ProductDetail() {
// 使用useParams获取路由参数
const { productId } = useParams();
return (
<div>
<h2>产品详情</h2>
<p>产品ID: {productId}</p>
</div>
);
}
// 用户个人资料页面
function UserProfile() {
const { username } = useParams();
const [user, setUser] = useState(null);
useEffect(() => {
// 根据username参数获取用户数据
fetchUser(username).then(setUser);
}, [username]);
return (
<div>
{user ? (
<div>
<h2>{user.name}的个人资料</h2>
<p>邮箱: {user.email}</p>
</div>
) : (
<p>加载中...</p>
)}
</div>
);
}
// 路由配置
function App() {
return (
<BrowserRouter>
<Routes>
<Route path="/" element={<Home />} />
<Route path="/products/:productId" element={<ProductDetail />} />
<Route path="/user/:username" element={<UserProfile />} />
{/* 可选参数 */}
<Route path="/posts/:postId?" element={<Post />} />
{/* 多个参数 */}
<Route path="/category/:categoryId/product/:productId" element={<Product />} />
</Routes>
</BrowserRouter>
);
}
用于声明式导航
<Link to="/home">首页</Link>
<Link to="/about" state={{ from: 'header' }}>关于</Link>
<Link to="../parent">返回上级</Link>
带活动状态的链接
<NavLink
to="/messages"
className={({ isActive }) =>
isActive ? 'active-link' : 'normal-link'
}
>
消息
</NavLink>
使用useNavigate
const navigate = useNavigate();
const handleClick = () => {
navigate('/home');
navigate(-1); // 后退
navigate('/user', { state: { id: 123 } });
};
// 认证上下文
const AuthContext = React.createContext();
function AuthProvider({ children }) {
const [user, setUser] = useState(null);
const login = (userData) => setUser(userData);
const logout = () => setUser(null);
return (
<AuthContext.Provider value={{ user, login, logout }}>
{children}
</AuthContext.Provider>
);
}
// 受保护的路由组件
function ProtectedRoute({ children }) {
const { user } = useContext(AuthContext);
const location = useLocation();
if (!user) {
// 重定向到登录页,并保存当前路径
return <Navigate to="/login" state={{ from: location }} replace />;
}
return children;
}
// 角色权限路由
function RoleProtectedRoute({ children, allowedRoles }) {
const { user } = useContext(AuthContext);
const location = useLocation();
if (!user) {
return <Navigate to="/login" state={{ from: location }} replace />;
}
if (!allowedRoles.includes(user.role)) {
return <Navigate to="/unauthorized" replace />;
}
return children;
}
// 路由配置
function App() {
return (
<AuthProvider>
<BrowserRouter>
<Routes>
<Route path="/" element={<Home />} />
<Route path="/login" element={<Login />} />
<Route path="/dashboard" element={
<ProtectedRoute>
<Dashboard />
</ProtectedRoute>
} />
<Route path="/admin" element={
<RoleProtectedRoute allowedRoles={['admin', 'superadmin']}>
<AdminPanel />
</RoleProtectedRoute>
} />
<Route path="/unauthorized" element={<Unauthorized />} />
</Routes>
</BrowserRouter>
</AuthProvider>
);
}
欢迎来到首页!请点击上面的按钮切换路由。
import React, { lazy, Suspense } from 'react';
import { BrowserRouter, Routes, Route } from 'react-router-dom';
// 使用lazy()进行组件懒加载
const Home = lazy(() => import('./pages/Home'));
const About = lazy(() => import('./pages/About'));
const Contact = lazy(() => import('./pages/Contact'));
const Dashboard = lazy(() => import('./pages/Dashboard'));
// 加载中组件
function LoadingSpinner() {
return (
<div style={{
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
height: '100vh'
}}>
<div className="spinner-border" role="status">
<span className="visually-hidden">加载中...</span>
</div>
</div>
);
}
function App() {
return (
<BrowserRouter>
<Suspense fallback={<LoadingSpinner />}>
<Routes>
<Route path="/" element={<Home />} />
<Route path="/about" element={<About />} />
<Route path="/contact" element={<Contact />} />
<Route path="/dashboard/*" element={<Dashboard />} />
</Routes>
</Suspense>
</BrowserRouter>
);
}
// 预加载组件(优化体验)
const preloadComponent = (importFunction) => {
const component = importFunction();
return component;
};
// 在用户可能访问前预加载
const preloadDashboard = () => preloadComponent(() => import('./pages/Dashboard'));
// 在鼠标悬停时预加载
<Link
to="/dashboard"
onMouseEnter={preloadDashboard}
>
仪表板
</Link>
| Hook | 用途 | 示例 |
|---|---|---|
useParams |
获取路由参数 | const { id } = useParams(); |
useNavigate |
编程式导航 | const navigate = useNavigate(); |
useLocation |
获取当前位置对象 | const location = useLocation(); |
useSearchParams |
获取和设置查询参数 | const [search, setSearch] = useSearchParams(); |
useRoutes |
声明式路由配置 | const routes = useRoutes(routeConfig); |
useOutlet |
获取嵌套路由的Outlet | const outlet = useOutlet(); |
useOutletContext |
访问Outlet传递的上下文 | const context = useOutletContext(); |
useResolvedPath |
解析相对路径 | const path = useResolvedPath('../parent'); |
import { useSearchParams, useLocation } from 'react-router-dom';
function ProductList() {
const [searchParams, setSearchParams] = useSearchParams();
const location = useLocation();
// 获取查询参数
const category = searchParams.get('category');
const sort = searchParams.get('sort') || 'newest';
const page = parseInt(searchParams.get('page')) || 1;
// 更新查询参数
const handleFilterChange = (newCategory) => {
setSearchParams({
category: newCategory,
page: 1, // 重置页码
sort
});
};
const handlePageChange = (newPage) => {
setSearchParams({
category,
page: newPage,
sort
});
};
// 构建查询字符串
const queryString = location.search;
return (
<div>
<h2>产品列表</h2>
<div>
<button onClick={() => handleFilterChange('electronics')}>
电子产品
</button>
<button onClick={() => handleFilterChange('clothing')}>
服装
</button>
</div>
<p>当前分类: {category || '全部'}</p>
<p>当前页码: {page}</p>
<button onClick={() => handlePageChange(page + 1)}>
下一页
</button>
</div>
);
}
const routes = [
{ path: '/', element: <Home /> },
{ path: '/about', element: <About /> },
{ path: '/contact', element: <Contact /> },
];
// routes/index.js
export const routes = [
{ path: '/', element: <Home /> },
{ path: '/dashboard/*', element: <DashboardLayout /> },
];
// routes/dashboard.js
export const dashboardRoutes = [
{ path: 'profile', element: <Profile /> },
{ path: 'settings', element: <Settings /> },
];
const userRoutes = users.map(user => ({
path: `/user/${user.id}`,
element: <UserProfile user={user} />,
}));
import { Routes, Route, useLocation } from 'react-router-dom';
import { AnimatePresence, motion } from 'framer-motion';
function AnimatedRoutes() {
const location = useLocation();
return (
<AnimatePresence mode="wait">
<Routes location={location} key={location.pathname}>
<Route path="/" element={
<motion.div
initial={{ opacity: 0, x: -100 }}
animate={{ opacity: 1, x: 0 }}
exit={{ opacity: 0, x: 100 }}
transition={{ duration: 0.5 }}
>
<Home />
</motion.div>
} />
<Route path="/about" element={
<motion.div
initial={{ opacity: 0, scale: 0.8 }}
animate={{ opacity: 1, scale: 1 }}
exit={{ opacity: 0, scale: 0.8 }}
transition={{ duration: 0.3 }}
>
<About />
</motion.div>
} />
</Routes>
</AnimatePresence>
);
}
// 使用CSS过渡
function CSSAnimatedRoutes() {
const location = useLocation();
return (
<div className="router-transition">
<Routes location={location}>
<Route path="/" element={<Home />} />
<Route path="/about" element={<About />} />
</Routes>
</div>
);
}
// CSS样式
.router-transition {
transition: opacity 0.3s ease;
}
.router-transition-enter {
opacity: 0;
}
.router-transition-enter-active {
opacity: 1;
}
<Outlet />useScrollRestoration