React 路由
概述
React Router 是 React 应用中最流行的路由库,它让我们能够创建单页应用(SPA)中的客户端路由。本章将学习如何使用 React Router 创建多页面应用。
🚀 基础路由配置
安装和基本设置
bash
npm install react-router-domjsx
import { BrowserRouter, Routes, Route, Link, useNavigate } from 'react-router-dom';
// 页面组件
function Home() {
return (
<div style={{ padding: '20px' }}>
<h1>首页</h1>
<p>欢迎来到我们的网站!</p>
</div>
);
}
function About() {
return (
<div style={{ padding: '20px' }}>
<h1>关于我们</h1>
<p>这是关于页面的内容。</p>
</div>
);
}
function Contact() {
const navigate = useNavigate();
return (
<div style={{ padding: '20px' }}>
<h1>联系我们</h1>
<p>联系信息:contact@example.com</p>
<button onClick={() => navigate('/')}>
返回首页
</button>
</div>
);
}
function NotFound() {
return (
<div style={{ padding: '20px', textAlign: 'center' }}>
<h1>404 - 页面未找到</h1>
<p>抱歉,您访问的页面不存在。</p>
<Link to="/">返回首页</Link>
</div>
);
}
// 导航组件
function Navigation() {
const navStyle = {
backgroundColor: '#343a40',
padding: '1rem'
};
const linkStyle = {
color: 'white',
textDecoration: 'none',
marginRight: '20px',
padding: '8px 16px',
borderRadius: '4px',
transition: 'background-color 0.3s'
};
return (
<nav style={navStyle}>
<Link to="/" style={linkStyle}>首页</Link>
<Link to="/about" style={linkStyle}>关于</Link>
<Link to="/contact" style={linkStyle}>联系</Link>
</nav>
);
}
// 主应用
function RouterBasicExample() {
return (
<BrowserRouter>
<div>
<Navigation />
<Routes>
<Route path="/" element={<Home />} />
<Route path="/about" element={<About />} />
<Route path="/contact" element={<Contact />} />
<Route path="*" element={<NotFound />} />
</Routes>
</div>
</BrowserRouter>
);
}🔗 动态路由和参数
路由参数
jsx
import { useParams, useSearchParams } from 'react-router-dom';
// 用户详情页
function UserDetail() {
const { userId } = useParams();
const [searchParams] = useSearchParams();
const tab = searchParams.get('tab') || 'profile';
// 模拟用户数据
const users = {
'1': { name: '张三', email: 'zhangsan@example.com', role: 'admin' },
'2': { name: '李四', email: 'lisi@example.com', role: 'user' },
'3': { name: '王五', email: 'wangwu@example.com', role: 'user' }
};
const user = users[userId];
if (!user) {
return <div>用户不存在</div>;
}
const renderTabContent = () => {
switch (tab) {
case 'profile':
return (
<div>
<h3>个人资料</h3>
<p>姓名: {user.name}</p>
<p>邮箱: {user.email}</p>
<p>角色: {user.role}</p>
</div>
);
case 'settings':
return (
<div>
<h3>设置</h3>
<p>这里是用户设置页面</p>
</div>
);
case 'orders':
return (
<div>
<h3>订单历史</h3>
<p>这里显示用户的订单历史</p>
</div>
);
default:
return <div>未知标签页</div>;
}
};
return (
<div style={{ padding: '20px' }}>
<h1>用户详情 - {user.name}</h1>
<div style={{ marginBottom: '20px' }}>
<Link
to={`/users/${userId}?tab=profile`}
style={{
marginRight: '10px',
padding: '8px 16px',
backgroundColor: tab === 'profile' ? '#007bff' : '#f8f9fa',
color: tab === 'profile' ? 'white' : '#333',
textDecoration: 'none',
borderRadius: '4px'
}}
>
个人资料
</Link>
<Link
to={`/users/${userId}?tab=settings`}
style={{
marginRight: '10px',
padding: '8px 16px',
backgroundColor: tab === 'settings' ? '#007bff' : '#f8f9fa',
color: tab === 'settings' ? 'white' : '#333',
textDecoration: 'none',
borderRadius: '4px'
}}
>
设置
</Link>
<Link
to={`/users/${userId}?tab=orders`}
style={{
padding: '8px 16px',
backgroundColor: tab === 'orders' ? '#007bff' : '#f8f9fa',
color: tab === 'orders' ? 'white' : '#333',
textDecoration: 'none',
borderRadius: '4px'
}}
>
订单
</Link>
</div>
<div style={{
border: '1px solid #ddd',
borderRadius: '8px',
padding: '20px',
backgroundColor: '#f8f9fa'
}}>
{renderTabContent()}
</div>
</div>
);
}
// 用户列表页
function UserList() {
const users = [
{ id: 1, name: '张三', role: 'admin' },
{ id: 2, name: '李四', role: 'user' },
{ id: 3, name: '王五', role: 'user' }
];
return (
<div style={{ padding: '20px' }}>
<h1>用户列表</h1>
<div style={{ display: 'grid', gap: '10px' }}>
{users.map(user => (
<div key={user.id} style={{
padding: '15px',
border: '1px solid #ddd',
borderRadius: '8px',
display: 'flex',
justifyContent: 'space-between',
alignItems: 'center'
}}>
<div>
<h3 style={{ margin: '0 0 5px 0' }}>{user.name}</h3>
<span style={{ color: '#666', fontSize: '14px' }}>{user.role}</span>
</div>
<Link
to={`/users/${user.id}`}
style={{
padding: '8px 16px',
backgroundColor: '#007bff',
color: 'white',
textDecoration: 'none',
borderRadius: '4px'
}}
>
查看详情
</Link>
</div>
))}
</div>
</div>
);
}🛡️ 路由守卫和权限
认证路由
jsx
// 认证上下文
const AuthContext = React.createContext();
function AuthProvider({ children }) {
const [user, setUser] = React.useState(null);
const [loading, setLoading] = React.useState(true);
React.useEffect(() => {
// 模拟检查登录状态
setTimeout(() => {
const savedUser = localStorage.getItem('user');
if (savedUser) {
setUser(JSON.parse(savedUser));
}
setLoading(false);
}, 1000);
}, []);
const login = (username, password) => {
// 模拟登录
if (username === 'admin' && password === 'password') {
const userData = { username: 'admin', role: 'admin' };
setUser(userData);
localStorage.setItem('user', JSON.stringify(userData));
return true;
}
return false;
};
const logout = () => {
setUser(null);
localStorage.removeItem('user');
};
return (
<AuthContext.Provider value={{ user, login, logout, loading }}>
{children}
</AuthContext.Provider>
);
}
function useAuth() {
return React.useContext(AuthContext);
}
// 受保护的路由组件
function ProtectedRoute({ children, requiredRole }) {
const { user, loading } = useAuth();
const navigate = useNavigate();
React.useEffect(() => {
if (!loading && !user) {
navigate('/login');
}
}, [user, loading, navigate]);
if (loading) {
return <div style={{ padding: '20px', textAlign: 'center' }}>加载中...</div>;
}
if (!user) {
return null;
}
if (requiredRole && user.role !== requiredRole) {
return (
<div style={{ padding: '20px', textAlign: 'center' }}>
<h2>权限不足</h2>
<p>您没有访问此页面的权限。</p>
</div>
);
}
return children;
}
// 登录页面
function Login() {
const [username, setUsername] = React.useState('');
const [password, setPassword] = React.useState('');
const [error, setError] = React.useState('');
const { login } = useAuth();
const navigate = useNavigate();
const handleSubmit = (e) => {
e.preventDefault();
if (login(username, password)) {
navigate('/dashboard');
} else {
setError('用户名或密码错误');
}
};
return (
<div style={{
padding: '20px',
maxWidth: '400px',
margin: '50px auto',
border: '1px solid #ddd',
borderRadius: '8px'
}}>
<h2>登录</h2>
<form onSubmit={handleSubmit}>
<div style={{ marginBottom: '15px' }}>
<input
type="text"
value={username}
onChange={(e) => setUsername(e.target.value)}
placeholder="用户名 (admin)"
style={{ width: '100%', padding: '8px' }}
/>
</div>
<div style={{ marginBottom: '15px' }}>
<input
type="password"
value={password}
onChange={(e) => setPassword(e.target.value)}
placeholder="密码 (password)"
style={{ width: '100%', padding: '8px' }}
/>
</div>
{error && <div style={{ color: 'red', marginBottom: '15px' }}>{error}</div>}
<button type="submit" style={{ width: '100%', padding: '10px' }}>
登录
</button>
</form>
</div>
);
}
// 仪表板页面
function Dashboard() {
const { user, logout } = useAuth();
return (
<div style={{ padding: '20px' }}>
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
<h1>仪表板</h1>
<div>
<span>欢迎, {user.username}!</span>
<button onClick={logout} style={{ marginLeft: '10px' }}>
登出
</button>
</div>
</div>
<p>这是受保护的仪表板页面。</p>
</div>
);
}
// 管理员页面
function AdminPanel() {
return (
<div style={{ padding: '20px' }}>
<h1>管理员面板</h1>
<p>只有管理员可以访问此页面。</p>
</div>
);
}🔄 路由状态管理
完整路由应用
jsx
function CompleteRoutingApp() {
return (
<AuthProvider>
<BrowserRouter>
<div>
<Routes>
<Route path="/login" element={<Login />} />
<Route path="/" element={<Home />} />
<Route path="/about" element={<About />} />
<Route path="/users" element={<UserList />} />
<Route path="/users/:userId" element={<UserDetail />} />
<Route
path="/dashboard"
element={
<ProtectedRoute>
<Dashboard />
</ProtectedRoute>
}
/>
<Route
path="/admin"
element={
<ProtectedRoute requiredRole="admin">
<AdminPanel />
</ProtectedRoute>
}
/>
<Route path="*" element={<NotFound />} />
</Routes>
</div>
</BrowserRouter>
</AuthProvider>
);
}📝 本章小结
通过本章学习,你应该掌握了:
路由核心概念
- ✅ BrowserRouter 和路由配置
- ✅ 动态路由和参数获取
- ✅ 路由守卫和权限控制
- ✅ 程序化导航
最佳实践
- 路由组织:合理规划路由结构
- 权限控制:实现路由级别的权限管理
- 用户体验:提供加载状态和错误页面
- 代码分割:使用懒加载优化性能