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. 如何优化路由性能?

扩展阅读

推荐资源

相关推荐
kyriewen25 分钟前
我用 Codex 重写了同事维护三年的代码,他没说谢谢——而是找了领导
前端·javascript·ai编程
OpenTiny社区37 分钟前
从零开发 AI 聊天页要两周?试试这款 Vue3 垂直对话组件库 TinyRobot,直接开箱即用
前端·vue.js·github
铁皮饭盒1 小时前
S3已成为文件存储标准,阿里/腾讯/华为云都支持,Bun率先原生支持
前端·javascript·后端
Cobyte1 小时前
22.Vue Vapor 组件 props 的实现
前端·javascript·vue.js
lichenyang4531 小时前
从 has.showToast 看 ASCF 的 API 调用链路
前端
张就是我1065922 小时前
DOMPurify 的一个漏洞:你以为 {} 是空的?
前端
疯狂的魔鬼3 小时前
一套 Schema 驱动四视图:记 useCrudSchemas 的设计与实践
前端·javascript·typescript
风骏时光牛马3 小时前
大模型开发工具高频故障与实操问题汇总代码案例大全
前端
没落英雄3 小时前
2. 让 Agent 能读写文件、执行命令 —— LocalShellBackend 实战
前端·人工智能·架构
白雾茫茫丶3 小时前
探索 Nuxt.js 全栈能力:用 Better-Auth 打造类型安全的 RBAC 权限系统
前端·vue.js·nuxt.js