Skip to content

React 路由

概述

React Router 是 React 应用中最流行的路由库,它让我们能够创建单页应用(SPA)中的客户端路由。本章将学习如何使用 React Router 创建多页面应用。

🚀 基础路由配置

安装和基本设置

bash
npm install react-router-dom
jsx
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 和路由配置
  • ✅ 动态路由和参数获取
  • ✅ 路由守卫和权限控制
  • ✅ 程序化导航

最佳实践

  1. 路由组织:合理规划路由结构
  2. 权限控制:实现路由级别的权限管理
  3. 用户体验:提供加载状态和错误页面
  4. 代码分割:使用懒加载优化性能

继续学习下一章 - React 状态管理 Redux

本站内容仅供学习和研究使用。