React 18.x 学习计划 - 第六天:React路由和导航

学习目标

  • 掌握React Router基础概念
  • 学会路由配置和导航
  • 理解动态路由和嵌套路由
  • 掌握路由守卫和权限控制
  • 构建完整的单页应用

学习时间安排

总时长:7-8小时

  • React Router基础:2小时
  • 路由配置和导航:2小时
  • 动态路由和嵌套路由:1.5小时
  • 路由守卫和权限控制:1小时
  • 实践项目:2-3小时

第一部分:React Router基础 (2小时)

1.1 安装和配置

安装React Router
bash 复制代码
# 安装React Router DOM
npm install react-router-dom

# 安装类型定义(如果使用TypeScript)
npm install --save-dev @types/react-router-dom
基础路由配置(详细注释版)
javascript 复制代码
// src/App.js
// 导入React
import React from 'react';
// 导入路由组件
import { BrowserRouter, Routes, Route, Link, Navigate } from 'react-router-dom';
// 导入页面组件
import Home from './pages/Home';
import About from './pages/About';
import Contact from './pages/Contact';
import NotFound from './pages/NotFound';
// 导入样式
import './App.css';

// 定义主应用组件
function App() {
  return (
    // 使用BrowserRouter包装整个应用
    // BrowserRouter使用HTML5 History API
    <BrowserRouter>
      <div className="App">
        {/* 导航栏 */}
        <nav className="navbar">
          <div className="nav-brand">
            <h1>My React App</h1>
          </div>
          <ul className="nav-links">
            {/* 使用Link组件创建导航链接 */}
            <li>
              <Link to="/">Home</Link>
            </li>
            <li>
              <Link to="/about">About</Link>
            </li>
            <li>
              <Link to="/contact">Contact</Link>
            </li>
          </ul>
        </nav>

        {/* 主要内容区域 */}
        <main className="main-content">
          {/* 使用Routes组件定义路由规则 */}
          <Routes>
            {/* 定义根路径路由 */}
            <Route path="/" element={<Home />} />
            
            {/* 定义关于页面路由 */}
            <Route path="/about" element={<About />} />
            
            {/* 定义联系页面路由 */}
            <Route path="/contact" element={<Contact />} />
            
            {/* 重定向路由 */}
            <Route path="/home" element={<Navigate to="/" replace />} />
            
            {/* 404页面路由 - 使用*匹配所有未匹配的路径 */}
            <Route path="*" element={<NotFound />} />
          </Routes>
        </main>

        {/* 页脚 */}
        <footer className="footer">
          <p>&copy; 2024 My React App. All rights reserved.</p>
        </footer>
      </div>
    </BrowserRouter>
  );
}

// 导出App组件
export default App;

1.2 基础页面组件

首页组件(详细注释版)
javascript 复制代码
// src/pages/Home.js
// 导入React
import React from 'react';
// 导入Link组件用于导航
import { Link } from 'react-router-dom';

// 定义Home组件
function Home() {
  return (
    <div className="home-page">
      <div className="hero-section">
        <h1>Welcome to My React App</h1>
        <p>This is the home page of our React application.</p>
        
        {/* 使用Link组件创建导航链接 */}
        <div className="cta-buttons">
          <Link to="/about" className="btn btn-primary">
            Learn More
          </Link>
          <Link to="/contact" className="btn btn-secondary">
            Contact Us
          </Link>
        </div>
      </div>
      
      <div className="features-section">
        <h2>Features</h2>
        <div className="features-grid">
          <div className="feature-card">
            <h3>React Router</h3>
            <p>Powerful routing for single-page applications</p>
          </div>
          <div className="feature-card">
            <h3>Modern UI</h3>
            <p>Beautiful and responsive user interface</p>
          </div>
          <div className="feature-card">
            <h3>Fast Performance</h3>
            <p>Optimized for speed and efficiency</p>
          </div>
        </div>
      </div>
    </div>
  );
}

// 导出Home组件
export default Home;
关于页面组件(详细注释版)
javascript 复制代码
// src/pages/About.js
// 导入React
import React from 'react';
// 导入Link组件
import { Link } from 'react-router-dom';

// 定义About组件
function About() {
  return (
    <div className="about-page">
      <div className="page-header">
        <h1>About Us</h1>
        <p>Learn more about our company and mission</p>
      </div>
      
      <div className="about-content">
        <section className="company-info">
          <h2>Our Company</h2>
          <p>
            We are a leading technology company focused on creating 
            innovative solutions for modern web applications. Our team 
            consists of experienced developers and designers who are 
            passionate about delivering high-quality products.
          </p>
        </section>
        
        <section className="team-info">
          <h2>Our Team</h2>
          <div className="team-grid">
            <div className="team-member">
              <h3>John Doe</h3>
              <p>CEO & Founder</p>
            </div>
            <div className="team-member">
              <h3>Jane Smith</h3>
              <p>CTO</p>
            </div>
            <div className="team-member">
              <h3>Bob Johnson</h3>
              <p>Lead Developer</p>
            </div>
          </div>
        </section>
        
        <section className="mission-info">
          <h2>Our Mission</h2>
          <p>
            To provide cutting-edge web solutions that help businesses 
            grow and succeed in the digital age. We believe in the power 
            of technology to transform ideas into reality.
          </p>
        </section>
      </div>
      
      <div className="page-actions">
        <Link to="/contact" className="btn btn-primary">
          Get in Touch
        </Link>
        <Link to="/" className="btn btn-secondary">
          Back to Home
        </Link>
      </div>
    </div>
  );
}

// 导出About组件
export default About;
联系页面组件(详细注释版)
javascript 复制代码
// src/pages/Contact.js
// 导入React和useState
import React, { useState } from 'react';
// 导入Link组件
import { Link } from 'react-router-dom';

// 定义Contact组件
function Contact() {
  // 使用useState Hook管理表单状态
  const [formData, setFormData] = useState({
    name: '',
    email: '',
    subject: '',
    message: ''
  });
  
  const [isSubmitting, setIsSubmitting] = useState(false);
  const [submitStatus, setSubmitStatus] = useState(null);

  // 处理输入变化
  const handleChange = (e) => {
    const { name, value } = e.target;
    setFormData(prev => ({
      ...prev,
      [name]: value
    }));
  };

  // 处理表单提交
  const handleSubmit = async (e) => {
    e.preventDefault();
    setIsSubmitting(true);
    
    try {
      // 模拟API调用
      await new Promise(resolve => setTimeout(resolve, 1000));
      
      setSubmitStatus('success');
      setFormData({ name: '', email: '', subject: '', message: '' });
    } catch (error) {
      setSubmitStatus('error');
    } finally {
      setIsSubmitting(false);
    }
  };

  return (
    <div className="contact-page">
      <div className="page-header">
        <h1>Contact Us</h1>
        <p>Get in touch with us</p>
      </div>
      
      <div className="contact-content">
        <div className="contact-info">
          <h2>Get in Touch</h2>
          <div className="contact-details">
            <div className="contact-item">
              <h3>Email</h3>
              <p>contact@myreactapp.com</p>
            </div>
            <div className="contact-item">
              <h3>Phone</h3>
              <p>+1 (555) 123-4567</p>
            </div>
            <div className="contact-item">
              <h3>Address</h3>
              <p>123 Main Street<br />City, State 12345</p>
            </div>
          </div>
        </div>
        
        <div className="contact-form">
          <h2>Send us a Message</h2>
          <form onSubmit={handleSubmit}>
            <div className="form-group">
              <label htmlFor="name">Name</label>
              <input
                type="text"
                id="name"
                name="name"
                value={formData.name}
                onChange={handleChange}
                required
              />
            </div>
            
            <div className="form-group">
              <label htmlFor="email">Email</label>
              <input
                type="email"
                id="email"
                name="email"
                value={formData.email}
                onChange={handleChange}
                required
              />
            </div>
            
            <div className="form-group">
              <label htmlFor="subject">Subject</label>
              <input
                type="text"
                id="subject"
                name="subject"
                value={formData.subject}
                onChange={handleChange}
                required
              />
            </div>
            
            <div className="form-group">
              <label htmlFor="message">Message</label>
              <textarea
                id="message"
                name="message"
                value={formData.message}
                onChange={handleChange}
                rows="5"
                required
              />
            </div>
            
            <button 
              type="submit" 
              className="btn btn-primary"
              disabled={isSubmitting}
            >
              {isSubmitting ? 'Sending...' : 'Send Message'}
            </button>
          </form>
          
          {submitStatus === 'success' && (
            <div className="success-message">
              Message sent successfully!
            </div>
          )}
          
          {submitStatus === 'error' && (
            <div className="error-message">
              Failed to send message. Please try again.
            </div>
          )}
        </div>
      </div>
      
      <div className="page-actions">
        <Link to="/" className="btn btn-secondary">
          Back to Home
        </Link>
      </div>
    </div>
  );
}

// 导出Contact组件
export default Contact;
404页面组件(详细注释版)
javascript 复制代码
// src/pages/NotFound.js
// 导入React
import React from 'react';
// 导入Link和useNavigate
import { Link, useNavigate } from 'react-router-dom';

// 定义NotFound组件
function NotFound() {
  // 使用useNavigate Hook获取导航函数
  const navigate = useNavigate();

  // 处理返回上一页
  const handleGoBack = () => {
    navigate(-1);
  };

  // 处理返回首页
  const handleGoHome = () => {
    navigate('/');
  };

  return (
    <div className="not-found-page">
      <div className="error-content">
        <h1>404</h1>
        <h2>Page Not Found</h2>
        <p>
          Sorry, the page you are looking for does not exist.
        </p>
        
        <div className="error-actions">
          <button onClick={handleGoBack} className="btn btn-secondary">
            Go Back
          </button>
          <button onClick={handleGoHome} className="btn btn-primary">
            Go Home
          </button>
          <Link to="/contact" className="btn btn-outline">
            Contact Support
          </Link>
        </div>
      </div>
    </div>
  );
}

// 导出NotFound组件
export default NotFound;

第二部分:路由配置和导航 (2小时)

2.1 路由参数和查询参数

动态路由组件(详细注释版)
javascript 复制代码
// src/pages/UserProfile.js
// 导入React和useState
import React, { useState, useEffect } from 'react';
// 导入路由Hooks
import { useParams, useSearchParams, useLocation } from 'react-router-dom';

// 定义UserProfile组件
function UserProfile() {
  // 使用useParams Hook获取路由参数
  const { userId } = useParams();
  
  // 使用useSearchParams Hook获取查询参数
  const [searchParams, setSearchParams] = useSearchParams();
  
  // 使用useLocation Hook获取位置信息
  const location = useLocation();
  
  // 使用useState Hook管理状态
  const [user, setUser] = useState(null);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState(null);

  // 从查询参数中获取值
  const tab = searchParams.get('tab') || 'profile';
  const edit = searchParams.get('edit') === 'true';

  // 获取用户数据
  useEffect(() => {
    const fetchUser = async () => {
      try {
        setLoading(true);
        // 模拟API调用
        const response = await fetch(`/api/users/${userId}`);
        if (!response.ok) {
          throw new Error('User not found');
        }
        const userData = await response.json();
        setUser(userData);
      } catch (err) {
        setError(err.message);
      } finally {
        setLoading(false);
      }
    };

    if (userId) {
      fetchUser();
    }
  }, [userId]);

  // 处理标签页切换
  const handleTabChange = (newTab) => {
    setSearchParams({ tab: newTab });
  };

  // 处理编辑模式切换
  const handleEditToggle = () => {
    setSearchParams(prev => ({
      ...Object.fromEntries(prev),
      edit: edit ? 'false' : 'true'
    }));
  };

  // 处理返回
  const handleGoBack = () => {
    navigate(-1);
  };

  if (loading) {
    return <div className="loading">Loading user profile...</div>;
  }

  if (error) {
    return (
      <div className="error">
        <h2>Error</h2>
        <p>{error}</p>
        <button onClick={handleGoBack}>Go Back</button>
      </div>
    );
  }

  if (!user) {
    return <div className="not-found">User not found</div>;
  }

  return (
    <div className="user-profile">
      <div className="profile-header">
        <button onClick={handleGoBack} className="back-btn">
          ← Back
        </button>
        <h1>User Profile: {user.name}</h1>
        <button onClick={handleEditToggle} className="edit-btn">
          {edit ? 'Cancel Edit' : 'Edit Profile'}
        </button>
      </div>
      
      <div className="profile-tabs">
        <button 
          className={tab === 'profile' ? 'active' : ''}
          onClick={() => handleTabChange('profile')}
        >
          Profile
        </button>
        <button 
          className={tab === 'settings' ? 'active' : ''}
          onClick={() => handleTabChange('settings')}
        >
          Settings
        </button>
        <button 
          className={tab === 'activity' ? 'active' : ''}
          onClick={() => handleTabChange('activity')}
        >
          Activity
        </button>
      </div>
      
      <div className="profile-content">
        {tab === 'profile' && (
          <div className="profile-info">
            <h2>Profile Information</h2>
            <div className="info-grid">
              <div className="info-item">
                <label>Name:</label>
                <span>{user.name}</span>
              </div>
              <div className="info-item">
                <label>Email:</label>
                <span>{user.email}</span>
              </div>
              <div className="info-item">
                <label>Phone:</label>
                <span>{user.phone}</span>
              </div>
              <div className="info-item">
                <label>Location:</label>
                <span>{user.location}</span>
              </div>
            </div>
          </div>
        )}
        
        {tab === 'settings' && (
          <div className="settings-info">
            <h2>Account Settings</h2>
            <p>Settings content goes here...</p>
          </div>
        )}
        
        {tab === 'activity' && (
          <div className="activity-info">
            <h2>Recent Activity</h2>
            <p>Activity content goes here...</p>
          </div>
        )}
      </div>
      
      <div className="profile-debug">
        <h3>Debug Information</h3>
        <p>User ID: {userId}</p>
        <p>Current Tab: {tab}</p>
        <p>Edit Mode: {edit ? 'Yes' : 'No'}</p>
        <p>Location: {location.pathname}</p>
        <p>Search: {location.search}</p>
      </div>
    </div>
  );
}

// 导出UserProfile组件
export default UserProfile;

2.2 嵌套路由

嵌套路由配置(详细注释版)
javascript 复制代码
// src/App.js
// 导入React
import React from 'react';
// 导入路由组件
import { BrowserRouter, Routes, Route, Outlet } from 'react-router-dom';
// 导入布局组件
import Layout from './components/Layout';
// 导入页面组件
import Home from './pages/Home';
import About from './pages/About';
import Contact from './pages/Contact';
import UserProfile from './pages/UserProfile';
import UserSettings from './pages/UserSettings';
import UserActivity from './pages/UserActivity';
import NotFound from './pages/NotFound';

// 定义主应用组件
function App() {
  return (
    <BrowserRouter>
      <Routes>
        {/* 根路径路由 */}
        <Route path="/" element={<Layout />}>
          {/* 嵌套路由 */}
          <Route index element={<Home />} />
          <Route path="about" element={<About />} />
          <Route path="contact" element={<Contact />} />
          
          {/* 用户相关嵌套路由 */}
          <Route path="user/:userId" element={<UserProfile />}>
            <Route path="settings" element={<UserSettings />} />
            <Route path="activity" element={<UserActivity />} />
          </Route>
        </Route>
        
        {/* 404页面路由 */}
        <Route path="*" element={<NotFound />} />
      </Routes>
    </BrowserRouter>
  );
}

// 导出App组件
export default App;
布局组件(详细注释版)
javascript 复制代码
// src/components/Layout.js
// 导入React
import React from 'react';
// 导入路由组件
import { Link, Outlet, useLocation } from 'react-router-dom';

// 定义Layout组件
function Layout() {
  // 使用useLocation Hook获取当前路径
  const location = useLocation();
  
  // 检查是否为活动路径
  const isActive = (path) => {
    return location.pathname === path;
  };

  return (
    <div className="layout">
      {/* 头部导航 */}
      <header className="header">
        <div className="header-content">
          <div className="logo">
            <Link to="/">My React App</Link>
          </div>
          <nav className="nav">
            <ul className="nav-list">
              <li>
                <Link 
                  to="/" 
                  className={isActive('/') ? 'active' : ''}
                >
                  Home
                </Link>
              </li>
              <li>
                <Link 
                  to="/about" 
                  className={isActive('/about') ? 'active' : ''}
                >
                  About
                </Link>
              </li>
              <li>
                <Link 
                  to="/contact" 
                  className={isActive('/contact') ? 'active' : ''}
                >
                  Contact
                </Link>
              </li>
            </ul>
          </nav>
        </div>
      </header>

      {/* 主要内容区域 */}
      <main className="main">
        {/* 使用Outlet组件渲染嵌套路由 */}
        <Outlet />
      </main>

      {/* 页脚 */}
      <footer className="footer">
        <div className="footer-content">
          <p>&copy; 2024 My React App. All rights reserved.</p>
          <div className="footer-links">
            <Link to="/">Home</Link>
            <Link to="/about">About</Link>
            <Link to="/contact">Contact</Link>
          </div>
        </div>
      </footer>
    </div>
  );
}

// 导出Layout组件
export default Layout;

2.3 编程式导航

导航Hook组件(详细注释版)
javascript 复制代码
// src/components/Navigation.js
// 导入React和useState
import React, { useState } from 'react';
// 导入路由Hooks
import { useNavigate, useLocation, useParams } from 'react-router-dom';

// 定义Navigation组件
function Navigation() {
  // 使用路由Hooks
  const navigate = useNavigate();
  const location = useLocation();
  const params = useParams();
  
  // 使用useState Hook管理状态
  const [searchQuery, setSearchQuery] = useState('');

  // 处理导航到特定路径
  const handleNavigate = (path) => {
    navigate(path);
  };

  // 处理返回上一页
  const handleGoBack = () => {
    navigate(-1);
  };

  // 处理前进到下一页
  const handleGoForward = () => {
    navigate(1);
  };

  // 处理搜索
  const handleSearch = (e) => {
    e.preventDefault();
    if (searchQuery.trim()) {
      navigate(`/search?q=${encodeURIComponent(searchQuery.trim())}`);
    }
  };

  // 处理用户导航
  const handleUserNavigate = (userId) => {
    navigate(`/user/${userId}`);
  };

  // 处理带状态的导航
  const handleNavigateWithState = (path, state) => {
    navigate(path, { state });
  };

  // 处理替换当前历史记录
  const handleReplace = (path) => {
    navigate(path, { replace: true });
  };

  // 处理相对导航
  const handleRelativeNavigate = (path) => {
    navigate(path, { relative: 'path' });
  };

  return (
    <div className="navigation">
      <div className="nav-controls">
        <button onClick={handleGoBack} className="nav-btn">
          ← Back
        </button>
        <button onClick={handleGoForward} className="nav-btn">
          Forward →
        </button>
      </div>
      
      <div className="nav-links">
        <button 
          onClick={() => handleNavigate('/')}
          className="nav-link"
        >
          Home
        </button>
        <button 
          onClick={() => handleNavigate('/about')}
          className="nav-link"
        >
          About
        </button>
        <button 
          onClick={() => handleNavigate('/contact')}
          className="nav-link"
        >
          Contact
        </button>
      </div>
      
      <div className="nav-actions">
        <button 
          onClick={() => handleUserNavigate(123)}
          className="nav-link"
        >
          User 123
        </button>
        <button 
          onClick={() => handleUserNavigate(456)}
          className="nav-link"
        >
          User 456
        </button>
      </div>
      
      <form onSubmit={handleSearch} className="search-form">
        <input
          type="text"
          placeholder="Search..."
          value={searchQuery}
          onChange={(e) => setSearchQuery(e.target.value)}
          className="search-input"
        />
        <button type="submit" className="search-btn">
          Search
        </button>
      </form>
      
      <div className="nav-info">
        <p>Current Path: {location.pathname}</p>
        <p>Search: {location.search}</p>
        <p>Params: {JSON.stringify(params)}</p>
        <p>State: {JSON.stringify(location.state)}</p>
      </div>
    </div>
  );
}

// 导出Navigation组件
export default Navigation;

第三部分:动态路由和嵌套路由 (1.5小时)

3.1 动态路由参数

产品详情页面(详细注释版)
javascript 复制代码
// src/pages/ProductDetail.js
// 导入React和useState
import React, { useState, useEffect } from 'react';
// 导入路由Hooks
import { useParams, useNavigate, useLocation } from 'react-router-dom';

// 定义ProductDetail组件
function ProductDetail() {
  // 使用路由Hooks
  const { productId, categoryId } = useParams();
  const navigate = useNavigate();
  const location = useLocation();
  
  // 使用useState Hook管理状态
  const [product, setProduct] = useState(null);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState(null);
  const [quantity, setQuantity] = useState(1);

  // 获取产品数据
  useEffect(() => {
    const fetchProduct = async () => {
      try {
        setLoading(true);
        // 模拟API调用
        const response = await fetch(`/api/products/${productId}`);
        if (!response.ok) {
          throw new Error('Product not found');
        }
        const productData = await response.json();
        setProduct(productData);
      } catch (err) {
        setError(err.message);
      } finally {
        setLoading(false);
      }
    };

    if (productId) {
      fetchProduct();
    }
  }, [productId]);

  // 处理添加到购物车
  const handleAddToCart = () => {
    // 模拟添加到购物车
    console.log(`Added ${quantity} of product ${productId} to cart`);
    navigate('/cart', { 
      state: { 
        product: product,
        quantity: quantity,
        from: location.pathname
      }
    });
  };

  // 处理购买
  const handleBuyNow = () => {
    navigate('/checkout', {
      state: {
        products: [{ product, quantity }],
        from: location.pathname
      }
    });
  };

  // 处理返回
  const handleGoBack = () => {
    if (location.state?.from) {
      navigate(location.state.from);
    } else if (categoryId) {
      navigate(`/category/${categoryId}`);
    } else {
      navigate('/products');
    }
  };

  if (loading) {
    return <div className="loading">Loading product...</div>;
  }

  if (error) {
    return (
      <div className="error">
        <h2>Error</h2>
        <p>{error}</p>
        <button onClick={handleGoBack}>Go Back</button>
      </div>
    );
  }

  if (!product) {
    return <div className="not-found">Product not found</div>;
  }

  return (
    <div className="product-detail">
      <div className="product-header">
        <button onClick={handleGoBack} className="back-btn">
          ← Back
        </button>
        <h1>{product.name}</h1>
      </div>
      
      <div className="product-content">
        <div className="product-image">
          <img src={product.image} alt={product.name} />
        </div>
        
        <div className="product-info">
          <h2>{product.name}</h2>
          <p className="product-price">${product.price}</p>
          <p className="product-description">{product.description}</p>
          
          <div className="product-details">
            <h3>Product Details</h3>
            <ul>
              <li>Category: {product.category}</li>
              <li>Brand: {product.brand}</li>
              <li>SKU: {product.sku}</li>
              <li>Stock: {product.stock}</li>
            </ul>
          </div>
          
          <div className="product-actions">
            <div className="quantity-selector">
              <label htmlFor="quantity">Quantity:</label>
              <input
                type="number"
                id="quantity"
                value={quantity}
                onChange={(e) => setQuantity(parseInt(e.target.value) || 1)}
                min="1"
                max={product.stock}
              />
            </div>
            
            <div className="action-buttons">
              <button 
                onClick={handleAddToCart}
                className="btn btn-primary"
              >
                Add to Cart
              </button>
              <button 
                onClick={handleBuyNow}
                className="btn btn-secondary"
              >
                Buy Now
              </button>
            </div>
          </div>
        </div>
      </div>
      
      <div className="product-debug">
        <h3>Debug Information</h3>
        <p>Product ID: {productId}</p>
        <p>Category ID: {categoryId}</p>
        <p>Current Path: {location.pathname}</p>
        <p>From: {location.state?.from || 'Unknown'}</p>
      </div>
    </div>
  );
}

// 导出ProductDetail组件
export default ProductDetail;

3.2 嵌套路由实现

用户设置页面(详细注释版)
javascript 复制代码
// src/pages/UserSettings.js
// 导入React和useState
import React, { useState } from 'react';
// 导入路由Hooks
import { useParams, useNavigate, useLocation, Outlet } from 'react-router-dom';

// 定义UserSettings组件
function UserSettings() {
  // 使用路由Hooks
  const { userId } = useParams();
  const navigate = useNavigate();
  const location = useLocation();
  
  // 使用useState Hook管理状态
  const [activeTab, setActiveTab] = useState('profile');

  // 处理标签页切换
  const handleTabChange = (tab) => {
    setActiveTab(tab);
    navigate(`/user/${userId}/settings/${tab}`);
  };

  // 处理返回
  const handleGoBack = () => {
    navigate(`/user/${userId}`);
  };

  return (
    <div className="user-settings">
      <div className="settings-header">
        <button onClick={handleGoBack} className="back-btn">
          ← Back to Profile
        </button>
        <h1>User Settings</h1>
      </div>
      
      <div className="settings-tabs">
        <button 
          className={activeTab === 'profile' ? 'active' : ''}
          onClick={() => handleTabChange('profile')}
        >
          Profile
        </button>
        <button 
          className={activeTab === 'account' ? 'active' : ''}
          onClick={() => handleTabChange('account')}
        >
          Account
        </button>
        <button 
          className={activeTab === 'security' ? 'active' : ''}
          onClick={() => handleTabChange('security')}
        >
          Security
        </button>
        <button 
          className={activeTab === 'notifications' ? 'active' : ''}
          onClick={() => handleTabChange('notifications')}
        >
          Notifications
        </button>
      </div>
      
      <div className="settings-content">
        {/* 使用Outlet组件渲染嵌套路由 */}
        <Outlet />
      </div>
    </div>
  );
}

// 导出UserSettings组件
export default UserSettings;
用户活动页面(详细注释版)
javascript 复制代码
// src/pages/UserActivity.js
// 导入React和useState
import React, { useState, useEffect } from 'react';
// 导入路由Hooks
import { useParams, useNavigate } from 'react-router-dom';

// 定义UserActivity组件
function UserActivity() {
  // 使用路由Hooks
  const { userId } = useParams();
  const navigate = useNavigate();
  
  // 使用useState Hook管理状态
  const [activities, setActivities] = useState([]);
  const [loading, setLoading] = useState(true);
  const [filter, setFilter] = useState('all');

  // 获取用户活动数据
  useEffect(() => {
    const fetchActivities = async () => {
      try {
        setLoading(true);
        // 模拟API调用
        const response = await fetch(`/api/users/${userId}/activities`);
        if (!response.ok) {
          throw new Error('Failed to fetch activities');
        }
        const activitiesData = await response.json();
        setActivities(activitiesData);
      } catch (error) {
        console.error('Error fetching activities:', error);
      } finally {
        setLoading(false);
      }
    };

    if (userId) {
      fetchActivities();
    }
  }, [userId]);

  // 处理返回
  const handleGoBack = () => {
    navigate(`/user/${userId}`);
  };

  // 过滤活动
  const filteredActivities = activities.filter(activity => {
    if (filter === 'all') return true;
    return activity.type === filter;
  });

  if (loading) {
    return <div className="loading">Loading activities...</div>;
  }

  return (
    <div className="user-activity">
      <div className="activity-header">
        <button onClick={handleGoBack} className="back-btn">
          ← Back to Profile
        </button>
        <h1>User Activity</h1>
      </div>
      
      <div className="activity-filters">
        <select 
          value={filter} 
          onChange={(e) => setFilter(e.target.value)}
        >
          <option value="all">All Activities</option>
          <option value="login">Logins</option>
          <option value="action">Actions</option>
          <option value="system">System</option>
        </select>
      </div>
      
      <div className="activity-list">
        {filteredActivities.length === 0 ? (
          <p>No activities found</p>
        ) : (
          filteredActivities.map(activity => (
            <div key={activity.id} className="activity-item">
              <div className="activity-icon">
                {activity.type === 'login' ? '🔐' : 
                 activity.type === 'action' ? '⚡' : '🔧'}
              </div>
              <div className="activity-content">
                <h3>{activity.title}</h3>
                <p>{activity.description}</p>
                <span className="activity-time">
                  {new Date(activity.timestamp).toLocaleString()}
                </span>
              </div>
            </div>
          ))
        )}
      </div>
    </div>
  );
}

// 导出UserActivity组件
export default UserActivity;

第四部分:路由守卫和权限控制 (1小时)

4.1 路由守卫实现

认证守卫组件(详细注释版)
javascript 复制代码
// src/components/ProtectedRoute.js
// 导入React
import React from 'react';
// 导入路由组件
import { Navigate, useLocation } from 'react-router-dom';
// 导入认证Hook
import { useAuth } from '../context/AuthContext';

// 定义ProtectedRoute组件
function ProtectedRoute({ children, requiredRole = null, requiredPermission = null }) {
  // 使用认证Hook
  const { user, isAuthenticated, hasRole, hasPermission } = useAuth();
  const location = useLocation();

  // 检查是否已认证
  if (!isAuthenticated) {
    // 重定向到登录页面,并保存当前路径
    return (
      <Navigate 
        to="/login" 
        state={{ from: location.pathname }} 
        replace 
      />
    );
  }

  // 检查角色权限
  if (requiredRole && !hasRole(requiredRole)) {
    return (
      <Navigate 
        to="/unauthorized" 
        state={{ 
          message: `You need ${requiredRole} role to access this page`,
          from: location.pathname
        }} 
        replace 
      />
    );
  }

  // 检查具体权限
  if (requiredPermission && !hasPermission(requiredPermission)) {
    return (
      <Navigate 
        to="/unauthorized" 
        state={{ 
          message: `You need ${requiredPermission} permission to access this page`,
          from: location.pathname
        }} 
        replace 
      />
    );
  }

  // 如果所有检查都通过,渲染子组件
  return children;
}

// 导出ProtectedRoute组件
export default ProtectedRoute;
权限控制组件(详细注释版)
javascript 复制代码
// src/components/PermissionGate.js
// 导入React
import React from 'react';
// 导入认证Hook
import { useAuth } from '../context/AuthContext';

// 定义PermissionGate组件
function PermissionGate({ 
  children, 
  role = null, 
  permission = null, 
  fallback = null,
  requireAll = false 
}) {
  // 使用认证Hook
  const { hasRole, hasPermission } = useAuth();

  // 检查角色权限
  const hasRoleAccess = role ? hasRole(role) : true;
  
  // 检查具体权限
  const hasPermissionAccess = permission ? hasPermission(permission) : true;

  // 根据requireAll参数决定权限检查逻辑
  const hasAccess = requireAll 
    ? hasRoleAccess && hasPermissionAccess
    : hasRoleAccess || hasPermissionAccess;

  // 如果没有权限,返回fallback或null
  if (!hasAccess) {
    return fallback;
  }

  // 如果有权限,渲染子组件
  return children;
}

// 导出PermissionGate组件
export default PermissionGate;

4.2 路由配置和权限

带权限的路由配置(详细注释版)
javascript 复制代码
// src/App.js
// 导入React
import React from 'react';
// 导入路由组件
import { BrowserRouter, Routes, Route } from 'react-router-dom';
// 导入布局组件
import Layout from './components/Layout';
// 导入守卫组件
import ProtectedRoute from './components/ProtectedRoute';
import PermissionGate from './components/PermissionGate';
// 导入页面组件
import Home from './pages/Home';
import Login from './pages/Login';
import Dashboard from './pages/Dashboard';
import AdminPanel from './pages/AdminPanel';
import UserProfile from './pages/UserProfile';
import Unauthorized from './pages/Unauthorized';
import NotFound from './pages/NotFound';

// 定义主应用组件
function App() {
  return (
    <BrowserRouter>
      <Routes>
        {/* 公共路由 */}
        <Route path="/" element={<Layout />}>
          <Route index element={<Home />} />
          <Route path="login" element={<Login />} />
          <Route path="unauthorized" element={<Unauthorized />} />
        </Route>

        {/* 受保护的路由 */}
        <Route path="/dashboard" element={
          <ProtectedRoute>
            <Dashboard />
          </ProtectedRoute>
        } />

        {/* 需要特定角色的路由 */}
        <Route path="/admin" element={
          <ProtectedRoute requiredRole="admin">
            <AdminPanel />
          </ProtectedRoute>
        } />

        {/* 需要特定权限的路由 */}
        <Route path="/user/:userId" element={
          <ProtectedRoute requiredPermission="read:users">
            <UserProfile />
          </ProtectedRoute>
        } />

        {/* 404页面路由 */}
        <Route path="*" element={<NotFound />} />
      </Routes>
    </BrowserRouter>
  );
}

// 导出App组件
export default App;
登录页面(详细注释版)
javascript 复制代码
// src/pages/Login.js
// 导入React和useState
import React, { useState } from 'react';
// 导入路由Hooks
import { useNavigate, useLocation } from 'react-router-dom';
// 导入认证Hook
import { useAuth } from '../context/AuthContext';

// 定义Login组件
function Login() {
  // 使用路由Hooks
  const navigate = useNavigate();
  const location = useLocation();
  
  // 使用认证Hook
  const { login, isLoading, error } = useAuth();
  
  // 使用useState Hook管理表单状态
  const [formData, setFormData] = useState({
    email: '',
    password: ''
  });

  // 获取重定向路径
  const from = location.state?.from || '/dashboard';

  // 处理输入变化
  const handleChange = (e) => {
    const { name, value } = e.target;
    setFormData(prev => ({
      ...prev,
      [name]: value
    }));
  };

  // 处理表单提交
  const handleSubmit = async (e) => {
    e.preventDefault();
    
    try {
      await login(formData);
      // 登录成功后重定向到原始路径
      navigate(from, { replace: true });
    } catch (error) {
      console.error('Login failed:', error);
    }
  };

  return (
    <div className="login-page">
      <div className="login-container">
        <h1>Login</h1>
        
        <form onSubmit={handleSubmit}>
          <div className="form-group">
            <label htmlFor="email">Email</label>
            <input
              type="email"
              id="email"
              name="email"
              value={formData.email}
              onChange={handleChange}
              required
            />
          </div>
          
          <div className="form-group">
            <label htmlFor="password">Password</label>
            <input
              type="password"
              id="password"
              name="password"
              value={formData.password}
              onChange={handleChange}
              required
            />
          </div>
          
          <button 
            type="submit" 
            className="btn btn-primary"
            disabled={isLoading}
          >
            {isLoading ? 'Logging in...' : 'Login'}
          </button>
        </form>
        
        {error && (
          <div className="error-message">
            {error}
          </div>
        )}
        
        <div className="login-info">
          <p>Redirecting to: {from}</p>
        </div>
      </div>
    </div>
  );
}

// 导出Login组件
export default Login;
未授权页面(详细注释版)
javascript 复制代码
// src/pages/Unauthorized.js
// 导入React
import React from 'react';
// 导入路由Hooks
import { useLocation, useNavigate } from 'react-router-dom';

// 定义Unauthorized组件
function Unauthorized() {
  // 使用路由Hooks
  const location = useLocation();
  const navigate = useNavigate();
  
  // 获取错误信息
  const message = location.state?.message || 'You are not authorized to access this page.';
  const from = location.state?.from || '/';

  // 处理返回
  const handleGoBack = () => {
    navigate(from);
  };

  // 处理返回首页
  const handleGoHome = () => {
    navigate('/');
  };

  return (
    <div className="unauthorized-page">
      <div className="error-content">
        <h1>403</h1>
        <h2>Unauthorized Access</h2>
        <p>{message}</p>
        
        <div className="error-actions">
          <button onClick={handleGoBack} className="btn btn-secondary">
            Go Back
          </button>
          <button onClick={handleGoHome} className="btn btn-primary">
            Go Home
          </button>
        </div>
      </div>
    </div>
  );
}

// 导出Unauthorized组件
export default Unauthorized;

第五部分:实践项目(详细注释版)

项目:完整的博客系统

主应用组件(详细注释版)
javascript 复制代码
// src/App.js
// 导入React
import React from 'react';
// 导入路由组件
import { BrowserRouter, Routes, Route } from 'react-router-dom';
// 导入布局组件
import Layout from './components/Layout';
// 导入守卫组件
import ProtectedRoute from './components/ProtectedRoute';
// 导入页面组件
import Home from './pages/Home';
import Blog from './pages/Blog';
import PostDetail from './pages/PostDetail';
import CreatePost from './pages/CreatePost';
import EditPost from './pages/EditPost';
import UserProfile from './pages/UserProfile';
import Login from './pages/Login';
import Register from './pages/Register';
import NotFound from './pages/NotFound';

// 定义主应用组件
function App() {
  return (
    <BrowserRouter>
      <Routes>
        {/* 公共路由 */}
        <Route path="/" element={<Layout />}>
          <Route index element={<Home />}
          <Route path="blog" element={<Blog />} />
          <Route path="blog/:postId" element={<PostDetail />} />
          <Route path="login" element={<Login />} />
          <Route path="register" element={<Register />} />
        </Route>

        {/* 受保护的路由 */}
        <Route path="/create-post" element={
          <ProtectedRoute requiredPermission="write:posts">
            <CreatePost />
          </ProtectedRoute>
        } />

        <Route path="/edit-post/:postId" element={
          <ProtectedRoute requiredPermission="write:posts">
            <EditPost />
          </ProtectedRoute>
        } />

        <Route path="/user/:userId" element={
          <ProtectedRoute>
            <UserProfile />
          </ProtectedRoute>
        } />

        {/* 404页面路由 */}
        <Route path="*" element={<NotFound />} />
      </Routes>
    </BrowserRouter>
  );
}

// 导出App组件
export default App;
博客列表页面(详细注释版)
javascript 复制代码
// src/pages/Blog.js
// 导入React和useState
import React, { useState, useEffect } from 'react';
// 导入路由Hooks
import { useNavigate, useSearchParams } from 'react-router-dom';
// 导入认证Hook
import { useAuth } from '../context/AuthContext';

// 定义Blog组件
function Blog() {
  // 使用路由Hooks
  const navigate = useNavigate();
  const [searchParams, setSearchParams] = useSearchParams();
  
  // 使用认证Hook
  const { hasPermission } = useAuth();
  
  // 使用useState Hook管理状态
  const [posts, setPosts] = useState([]);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState(null);
  const [searchQuery, setSearchQuery] = useState('');
  const [category, setCategory] = useState('all');

  // 从URL参数中获取搜索查询
  useEffect(() => {
    const query = searchParams.get('q') || '';
    const cat = searchParams.get('category') || 'all';
    setSearchQuery(query);
    setCategory(cat);
  }, [searchParams]);

  // 获取博客文章
  useEffect(() => {
    const fetchPosts = async () => {
      try {
        setLoading(true);
        // 模拟API调用
        const response = await fetch('/api/posts');
        if (!response.ok) {
          throw new Error('Failed to fetch posts');
        }
        const postsData = await response.json();
        setPosts(postsData);
      } catch (err) {
        setError(err.message);
      } finally {
        setLoading(false);
      }
    };

    fetchPosts();
  }, []);

  // 处理搜索
  const handleSearch = (e) => {
    e.preventDefault();
    const params = new URLSearchParams();
    if (searchQuery.trim()) {
      params.set('q', searchQuery.trim());
    }
    if (category !== 'all') {
      params.set('category', category);
    }
    setSearchParams(params);
  };

  // 处理分类过滤
  const handleCategoryChange = (newCategory) => {
    setCategory(newCategory);
    const params = new URLSearchParams();
    if (searchQuery.trim()) {
      params.set('q', searchQuery.trim());
    }
    if (newCategory !== 'all') {
      params.set('category', newCategory);
    }
    setSearchParams(params);
  };

  // 处理文章点击
  const handlePostClick = (postId) => {
    navigate(`/blog/${postId}`);
  };

  // 处理创建文章
  const handleCreatePost = () => {
    navigate('/create-post');
  };

  // 过滤文章
  const filteredPosts = posts.filter(post => {
    const matchesSearch = !searchQuery.trim() || 
      post.title.toLowerCase().includes(searchQuery.toLowerCase()) ||
      post.content.toLowerCase().includes(searchQuery.toLowerCase());
    
    const matchesCategory = category === 'all' || post.category === category;
    
    return matchesSearch && matchesCategory;
  });

  if (loading) {
    return <div className="loading">Loading posts...</div>;
  }

  if (error) {
    return <div className="error">Error: {error}</div>;
  }

  return (
    <div className="blog-page">
      <div className="blog-header">
        <h1>Blog</h1>
        {hasPermission('write:posts') && (
          <button onClick={handleCreatePost} className="btn btn-primary">
            Create Post
          </button>
        )}
      </div>
      
      <div className="blog-filters">
        <form onSubmit={handleSearch} className="search-form">
          <input
            type="text"
            placeholder="Search posts..."
            value={searchQuery}
            onChange={(e) => setSearchQuery(e.target.value)}
            className="search-input"
          />
          <button type="submit" className="search-btn">Search</button>
        </form>
        
        <div className="category-filters">
          <button 
            className={category === 'all' ? 'active' : ''}
            onClick={() => handleCategoryChange('all')}
          >
            All
          </button>
          <button 
            className={category === 'technology' ? 'active' : ''}
            onClick={() => handleCategoryChange('technology')}
          >
            Technology
          </button>
          <button 
            className={category === 'lifestyle' ? 'active' : ''}
            onClick={() => handleCategoryChange('lifestyle')}
          >
            Lifestyle
          </button>
          <button 
            className={category === 'business' ? 'active' : ''}
            onClick={() => handleCategoryChange('business')}
          >
            Business
          </button>
        </div>
      </div>
      
      <div className="blog-content">
        {filteredPosts.length === 0 ? (
          <div className="no-posts">
            <h2>No posts found</h2>
            <p>Try adjusting your search or filter criteria.</p>
          </div>
        ) : (
          <div className="posts-grid">
            {filteredPosts.map(post => (
              <div 
                key={post.id} 
                className="post-card"
                onClick={() => handlePostClick(post.id)}
              >
                <div className="post-image">
                  <img src={post.image} alt={post.title} />
                </div>
                <div className="post-content">
                  <h3>{post.title}</h3>
                  <p>{post.excerpt}</p>
                  <div className="post-meta">
                    <span className="post-author">{post.author}</span>
                    <span className="post-date">
                      {new Date(post.createdAt).toLocaleDateString()}
                    </span>
                    <span className="post-category">{post.category}</span>
                  </div>
                </div>
              </div>
            ))}
          </div>
        )}
      </div>
    </div>
  );
}

// 导出Blog组件
export default Blog;
文章详情页面(详细注释版)
javascript 复制代码
// src/pages/PostDetail.js
// 导入React和useState
import React, { useState, useEffect } from 'react';
// 导入路由Hooks
import { useParams, useNavigate, useLocation } from 'react-router-dom';
// 导入认证Hook
import { useAuth } from '../context/AuthContext';

// 定义PostDetail组件
function PostDetail() {
  // 使用路由Hooks
  const { postId } = useParams();
  const navigate = useNavigate();
  const location = useLocation();
  
  // 使用认证Hook
  const { user, hasPermission } = useAuth();
  
  // 使用useState Hook管理状态
  const [post, setPost] = useState(null);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState(null);
  const [comments, setComments] = useState([]);
  const [newComment, setNewComment] = useState('');

  // 获取文章详情
  useEffect(() => {
    const fetchPost = async () => {
      try {
        setLoading(true);
        // 模拟API调用
        const response = await fetch(`/api/posts/${postId}`);
        if (!response.ok) {
          throw new Error('Post not found');
        }
        const postData = await response.json();
        setPost(postData);
      } catch (err) {
        setError(err.message);
      } finally {
        setLoading(false);
      }
    };

    if (postId) {
      fetchPost();
    }
  }, [postId]);

  // 获取评论
  useEffect(() => {
    const fetchComments = async () => {
      try {
        const response = await fetch(`/api/posts/${postId}/comments`);
        if (response.ok) {
          const commentsData = await response.json();
          setComments(commentsData);
        }
      } catch (err) {
        console.error('Error fetching comments:', err);
      }
    };

    if (postId) {
      fetchComments();
    }
  }, [postId]);

  // 处理添加评论
  const handleAddComment = async (e) => {
    e.preventDefault();
    if (!newComment.trim()) return;

    try {
      const response = await fetch(`/api/posts/${postId}/comments`, {
        method: 'POST',
        headers: {
          'Content-Type': 'application/json',
        },
        body: JSON.stringify({
          content: newComment,
          author: user?.name || 'Anonymous'
        })
      });

      if (response.ok) {
        const comment = await response.json();
        setComments(prev => [...prev, comment]);
        setNewComment('');
      }
    } catch (err) {
      console.error('Error adding comment:', err);
    }
  };

  // 处理编辑文章
  const handleEditPost = () => {
    navigate(`/edit-post/${postId}`);
  };

  // 处理删除文章
  const handleDeletePost = async () => {
    if (!window.confirm('Are you sure you want to delete this post?')) {
      return;
    }

    try {
      const response = await fetch(`/api/posts/${postId}`, {
        method: 'DELETE'
      });

      if (response.ok) {
        navigate('/blog');
      }
    } catch (err) {
      console.error('Error deleting post:', err);
    }
  };

  // 处理返回
  const handleGoBack = () => {
    if (location.state?.from) {
      navigate(location.state.from);
    } else {
      navigate('/blog');
    }
  };

  if (loading) {
    return <div className="loading">Loading post...</div>;
  }

  if (error) {
    return (
      <div className="error">
        <h2>Error</h2>
        <p>{error}</p>
        <button onClick={handleGoBack}>Go Back</button>
      </div>
    );
  }

  if (!post) {
    return <div className="not-found">Post not found</div>;
  }

  return (
    <div className="post-detail">
      <div className="post-header">
        <button onClick={handleGoBack} className="back-btn">
          ← Back to Blog
        </button>
        <h1>{post.title}</h1>
        {hasPermission('write:posts') && (
          <div className="post-actions">
            <button onClick={handleEditPost} className="btn btn-secondary">
              Edit
            </button>
            <button onClick={handleDeletePost} className="btn btn-danger">
              Delete
            </button>
          </div>
        )}
      </div>
      
      <div className="post-content">
        <div className="post-meta">
          <span className="post-author">By {post.author}</span>
          <span className="post-date">
            {new Date(post.createdAt).toLocaleDateString()}
          </span>
          <span className="post-category">{post.category}</span>
        </div>
        
        <div className="post-image">
          <img src={post.image} alt={post.title} />
        </div>
        
        <div className="post-body">
          {post.content}
        </div>
      </div>
      
      <div className="post-comments">
        <h2>Comments ({comments.length})</h2>
        
        <div className="comments-list">
          {comments.map(comment => (
            <div key={comment.id} className="comment">
              <div className="comment-header">
                <span className="comment-author">{comment.author}</span>
                <span className="comment-date">
                  {new Date(comment.createdAt).toLocaleDateString()}
                </span>
              </div>
              <div className="comment-content">
                {comment.content}
              </div>
            </div>
          ))}
        </div>
        
        <form onSubmit={handleAddComment} className="comment-form">
          <textarea
            value={newComment}
            onChange={(e) => setNewComment(e.target.value)}
            placeholder="Write a comment..."
            rows="3"
          />
          <button type="submit" className="btn btn-primary">
            Add Comment
          </button>
        </form>
      </div>
    </div>
  );
}

// 导出PostDetail组件
export default PostDetail;
样式文件(详细注释版)
css 复制代码
/* src/App.css */
/* 应用主容器样式 */
.App {
  min-height: 100vh;
  display: flex;
  flex-direction: column;
  font-family: Arial, sans-serif;
}

/* 布局样式 */
.layout {
  min-height: 100vh;
  display: flex;
  flex-direction: column;
}

.header {
  background-color: #333;
  color: white;
  padding: 1rem;
}

.header-content {
  display: flex;
  justify-content: space-between;
  align-items: center;
  max-width: 1200px;
  margin: 0 auto;
}

.logo a {
  color: white;
  text-decoration: none;
  font-size: 1.5rem;
  font-weight: bold;
}

.nav-list {
  display: flex;
  list-style: none;
  margin: 0;
  padding: 0;
  gap: 2rem;
}

.nav-list a {
  color: white;
  text-decoration: none;
  padding: 0.5rem 1rem;
  border-radius: 4px;
  transition: background-color 0.3s;
}

.nav-list a:hover,
.nav-list a.active {
  background-color: #555;
}

.main {
  flex: 1;
  padding: 2rem;
  max-width: 1200px;
  margin: 0 auto;
  width: 100%;
  box-sizing: border-box;
}

.footer {
  background-color: #f8f9fa;
  padding: 2rem;
  text-align: center;
  border-top: 1px solid #dee2e6;
}

.footer-content {
  max-width: 1200px;
  margin: 0 auto;
  display: flex;
  justify-content: space-between;
  align-items: center;
}

.footer-links {
  display: flex;
  gap: 1rem;
}

.footer-links a {
  color: #6c757d;
  text-decoration: none;
}

/* 页面样式 */
.home-page {
  text-align: center;
}

.hero-section {
  padding: 4rem 0;
  background-color: #f8f9fa;
  border-radius: 8px;
  margin-bottom: 3rem;
}

.hero-section h1 {
  font-size: 3rem;
  margin-bottom: 1rem;
  color: #333;
}

.hero-section p {
  font-size: 1.2rem;
  color: #666;
  margin-bottom: 2rem;
}

.cta-buttons {
  display: flex;
  gap: 1rem;
  justify-content: center;
}

.features-section {
  margin-top: 3rem;
}

.features-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
  gap: 2rem;
  margin-top: 2rem;
}

.feature-card {
  background-color: white;
  padding: 2rem;
  border-radius: 8px;
  box-shadow: 0 2px 4px rgba(0,0,0,0.1);
  text-align: center;
}

.feature-card h3 {
  color: #333;
  margin-bottom: 1rem;
}

.feature-card p {
  color: #666;
  line-height: 1.6;
}

/* 博客页面样式 */
.blog-page {
  max-width: 1200px;
  margin: 0 auto;
}

.blog-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  margin-bottom: 2rem;
}

.blog-header h1 {
  margin: 0;
  color: #333;
}

.blog-filters {
  display: flex;
  flex-direction: column;
  gap: 1rem;
  margin-bottom: 2rem;
  padding: 1rem;
  background-color: #f8f9fa;
  border-radius: 8px;
}

.search-form {
  display: flex;
  gap: 0.5rem;
}

.search-input {
  flex: 1;
  padding: 0.5rem;
  border: 1px solid #ddd;
  border-radius: 4px;
  font-size: 1rem;
}

.search-btn {
  padding: 0.5rem 1rem;
  background-color: #007bff;
  color: white;
  border: none;
  border-radius: 4px;
  cursor: pointer;
}

.category-filters {
  display: flex;
  gap: 0.5rem;
  flex-wrap: wrap;
}

.category-filters button {
  padding: 0.5rem 1rem;
  border: 1px solid #ddd;
  background-color: white;
  border-radius: 4px;
  cursor: pointer;
  transition: all 0.3s;
}

.category-filters button:hover,
.category-filters button.active {
  background-color: #007bff;
  color: white;
  border-color: #007bff;
}

.posts-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
  gap: 2rem;
}

.post-card {
  background-color: white;
  border-radius: 8px;
  overflow: hidden;
  box-shadow: 0 2px 4px rgba(0,0,0,0.1);
  cursor: pointer;
  transition: transform 0.3s, box-shadow 0.3s;
}

.post-card:hover {
  transform: translateY(-2px);
  box-shadow: 0 4px 8px rgba(0,0,0,0.15);
}

.post-image img {
  width: 100%;
  height: 200px;
  object-fit: cover;
}

.post-content {
  padding: 1.5rem;
}

.post-content h3 {
  margin: 0 0 1rem 0;
  color: #333;
  font-size: 1.2rem;
}

.post-content p {
  color: #666;
  line-height: 1.6;
  margin-bottom: 1rem;
}

.post-meta {
  display: flex;
  justify-content: space-between;
  align-items: center;
  font-size: 0.9rem;
  color: #999;
}

.post-meta span {
  background-color: #f8f9fa;
  padding: 0.25rem 0.5rem;
  border-radius: 4px;
}

/* 文章详情页面样式 */
.post-detail {
  max-width: 800px;
  margin: 0 auto;
}

.post-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  margin-bottom: 2rem;
  padding-bottom: 1rem;
  border-bottom: 1px solid #dee2e6;
}

.post-header h1 {
  margin: 0;
  color: #333;
  flex: 1;
}

.post-actions {
  display: flex;
  gap: 0.5rem;
}

.post-content {
  margin-bottom: 3rem;
}

.post-meta {
  display: flex;
  gap: 1rem;
  margin-bottom: 2rem;
  font-size: 0.9rem;
  color: #666;
}

.post-image img {
  width: 100%;
  height: 400px;
  object-fit: cover;
  border-radius: 8px;
  margin-bottom: 2rem;
}

.post-body {
  font-size: 1.1rem;
  line-height: 1.8;
  color: #333;
}

.post-comments {
  border-top: 1px solid #dee2e6;
  padding-top: 2rem;
}

.post-comments h2 {
  margin-bottom: 1.5rem;
  color: #333;
}

.comments-list {
  margin-bottom: 2rem;
}

.comment {
  background-color: #f8f9fa;
  padding: 1rem;
  border-radius: 8px;
  margin-bottom: 1rem;
}

.comment-header {
  display: flex;
  justify-content: space-between;
  margin-bottom: 0.5rem;
  font-size: 0.9rem;
  color: #666;
}

.comment-content {
  color: #333;
  line-height: 1.6;
}

.comment-form {
  display: flex;
  flex-direction: column;
  gap: 1rem;
}

.comment-form textarea {
  padding: 0.5rem;
  border: 1px solid #ddd;
  border-radius: 4px;
  font-size: 1rem;
  resize: vertical;
}

/* 按钮样式 */
.btn {
  padding: 0.5rem 1rem;
  border: none;
  border-radius: 4px;
  cursor: pointer;
  font-size: 1rem;
  text-decoration: none;
  display: inline-block;
  text-align: center;
  transition: background-color 0.3s;
}

.btn-primary {
  background-color: #007bff;
  color: white;
}

.btn-primary:hover {
  background-color: #0056b3;
}

.btn-secondary {
  background-color: #6c757d;
  color: white;
}

.btn-secondary:hover {
  background-color: #545b62;
}

.btn-danger {
  background-color: #dc3545;
  color: white;
}

.btn-danger:hover {
  background-color: #c82333;
}

.btn-outline {
  background-color: transparent;
  color: #007bff;
  border: 1px solid #007bff;
}

.btn-outline:hover {
  background-color: #007bff;
  color: white;
}

/* 工具类 */
.loading {
  text-align: center;
  padding: 2rem;
  font-size: 1.2rem;
  color: #666;
}

.error {
  text-align: center;
  padding: 2rem;
  color: #dc3545;
}

.not-found {
  text-align: center;
  padding: 2rem;
  color: #666;
}

.back-btn {
  background-color: #6c757d;
  color: white;
  border: none;
  padding: 0.5rem 1rem;
  border-radius: 4px;
  cursor: pointer;
  margin-bottom: 1rem;
}

.back-btn:hover {
  background-color: #545b62;
}

/* 响应式设计 */
@media (max-width: 768px) {
  .header-content {
    flex-direction: column;
    gap: 1rem;
  }
  
  .nav-list {
    flex-direction: column;
    gap: 0.5rem;
  }
  
  .main {
    padding: 1rem;
  }
  
  .blog-header {
    flex-direction: column;
    gap: 1rem;
    align-items: stretch;
  }
  
  .search-form {
    flex-direction: column;
  }
  
  .category-filters {
    justify-content: center;
  }
  
  .posts-grid {
    grid-template-columns: 1fr;
  }
  
  .post-header {
    flex-direction: column;
    gap: 1rem;
    align-items: stretch;
  }
  
  .post-actions {
    justify-content: center;
  }
  
  .footer-content {
    flex-direction: column;
    gap: 1rem;
  }
}

练习题目

基础练习

  1. 路由基础练习
javascript 复制代码
// 练习1:创建一个简单的导航系统
// 实现:首页、关于、联系、404页面
// 包含:导航栏、页脚、响应式设计

// 练习2:实现动态路由
// 实现:用户详情页、产品详情页
// 包含:路由参数、查询参数、编程式导航
  1. 嵌套路由练习
javascript 复制代码
// 练习3:实现用户管理系统的嵌套路由
// 实现:用户列表、用户详情、用户设置
// 包含:嵌套布局、Outlet组件

// 练习4:实现博客系统的嵌套路由
// 实现:文章列表、文章详情、评论系统
// 包含:动态路由、状态管理

进阶练习

  1. 路由守卫练习
javascript 复制代码
// 练习5:实现完整的权限控制系统
// 实现:登录、注册、权限验证
// 包含:路由守卫、权限控制、状态管理

// 练习6:实现企业级应用的路由系统
// 实现:多级路由、权限控制、状态管理
// 包含:最佳实践、性能优化
  1. 综合应用练习
javascript 复制代码
// 练习7:构建完整的电商系统
// 实现:商品展示、购物车、订单管理
// 包含:路由配置、状态管理、权限控制

学习检查点

完成标准

  • 理解React Router基础概念
  • 掌握路由配置和导航
  • 学会动态路由和嵌套路由
  • 掌握路由守卫和权限控制
  • 完成所有练习题目

自我测试

  1. React Router的核心概念是什么?
  2. 如何实现动态路由和嵌套路由?
  3. 路由守卫的作用是什么?
  4. 如何优化路由性能?

扩展阅读

推荐资源

相关推荐
yuxb733 小时前
Zabbix企业级分布式监控系统(上)
笔记·学习·zabbix
fruge5 小时前
Vue项目中的Electron桌面应用开发实践指南
前端·vue.js·electron
Chloeis Syntax10 小时前
MySQL初阶学习日记(1)--- 数据库的基本操作
数据库·学习·mysql
漂流瓶jz11 小时前
Webpack中各种devtool配置的含义与SourceMap生成逻辑
前端·javascript·webpack
前端架构师-老李11 小时前
React 中 useCallback 的基本使用和原理解析
前端·react.js·前端框架
musenh11 小时前
css样式学习
css·学习·css3
木易 士心11 小时前
CSS 中 `data-status` 的使用详解
前端·css
Larry_Yanan11 小时前
QML学习笔记(五十)QML与C++交互:QML中单例C++对象
开发语言·c++·笔记·qt·学习·ui·交互
im_AMBER11 小时前
算法笔记 09
c语言·数据结构·c++·笔记·学习·算法·排序算法