React 全栈实战:从组件到鉴权的完整指南

基于一个完整的 JWT 登录鉴权 Demo,带你系统掌握 React 核心概念与工程实践

前言

React 是当前最流行的前端框架之一,但很多初学者在学习过程中会遇到这样的问题:

  • 看了很多教程,但不知道如何组织一个完整的项目
  • 理解了组件、状态等概念,但不知道如何应用到实际场景
  • 知道 React Router、Zustand、Axios,但不知道如何协同工作

本文将通过一个完整的 JWT 登录鉴权项目,带你从零到一构建一个 React 应用。你将学到:

  • React 基础:组件、JSX、Props、状态管理
  • 路由系统:React Router、懒加载、路由守卫
  • 状态管理:useState、useEffect、Zustand 全局状态
  • API 层:Axios 封装、拦截器、JWT 鉴权
  • 工程实践:项目结构、代码组织、最佳实践

一、项目架构总览

1.1 目录结构

bash 复制代码
login-demo/
├── mock/                    # Mock 服务(模拟后端)
│   └── user.js             # JWT 签发与验证
├── src/
│   ├── api/                # API 层
│   │   ├── config.js      # Axios 实例 + 拦截器
│   │   ├── user.js        # 登录 API
│   │   └── repo.js        # 受保护资源 API
│   ├── components/         # 公共组件
│   │   ├── Nav.jsx        # 导航栏
│   │   └── RequireAuth.jsx # 路由守卫
│   ├── pages/              # 页面组件
│   │   ├── Home.jsx       # 首页
│   │   ├── Login.jsx      # 登录页
│   │   └── Pay.jsx        # 支付页(受保护)
│   ├── store/              # 全局状态
│   │   └── user.js        # 用户状态(Zustand)
│   ├── App.jsx             # 路由配置
│   └── main.jsx            # 入口文件
└── vite.config.js          # Vite 配置 + Mock 插件

1.2 技术栈

  • React 19 - UI 框架
  • React Router DOM - 路由管理
  • Zustand - 轻量级状态管理
  • Axios - HTTP 请求
  • JWT - 登录鉴权
  • Vite - 构建工具
  • vite-plugin-mock - Mock 服务

1.3 完整流程图

javascript 复制代码
用户登录 → Mock 签发 JWT → 存储到 localStorage + Zustand
    ↓
后续请求 → Axios 拦截器自动加 Authorization: Bearer xxx
    ↓
访问受保护页面 → RequireAuth 检查 token → 有则放行,无则重定向
    ↓
请求受保护资源 → Mock 验证 JWT → 返回数据

二、React 基础:组件与 JSX

2.1 什么是组件?

React 应用由组件组成。组件是独立的、可复用的 UI 单元。

jsx 复制代码
// 一个简单的组件
function Home() {
  return <div>Home 首页</div>;
}

关键点:

  • 组件名必须大写开头(React 通过大小写区分组件和 HTML 标签)
  • 组件返回 JSX(JavaScript XML,类似 HTML 的语法)
  • 组件可以接收 props(父组件传入的数据)

2.2 JSX 语法

JSX 是 React 的核心语法,它允许你在 JavaScript 中写类似 HTML 的代码。

jsx 复制代码
function Nav() {
  const token = 'xxx'; // 假设有 token
  
  return (
    <nav>
      {/* 条件渲染 */}
      {!token && <a href="/login">Login</a>}
      
      {/* 变量插值 */}
      {token && <span>已登录</span>}
      
      {/* 事件绑定 */}
      <button onClick={() => console.log('clicked')}>点击</button>
    </nav>
  );
}

关键点:

  • JSX 中用 {} 包裹 JavaScript 表达式
  • 支持条件渲染(&&、三元运算符)
  • 支持列表渲染(map
  • 事件名用驼峰命名(onClick 而非 onclick

2.3 Props:组件间通信

Props 是父组件向子组件传递数据的方式。

jsx 复制代码
// 父组件
function App() {
  return <Pay user="admin" amount={100} />;
}

// 子组件
function Pay({ user, amount }) {
  return (
    <div>
      <p>用户: {user}</p>
      <p>金额: ¥{amount}</p>
    </div>
  );
}

关键点:

  • Props 是只读的,子组件不能修改
  • 可以传递任意类型的数据(字符串、数字、对象、函数、组件)
  • 通过解构赋值简化 props 使用

三、状态管理:useState 与 useEffect

3.1 useState:组件内部状态

useState 是 React 最常用的 Hook,用于管理组件内部状态。

jsx 复制代码
import { useState } from 'react';

function Pay() {
  const [amount, setAmount] = useState('');
  const [paid, setPaid] = useState(false);

  const handlePay = (e) => {
    e.preventDefault();
    if (amount && Number(amount) > 0) {
      setPaid(true);
    }
  };

  return (
    <form onSubmit={handlePay}>
      <input
        type="number"
        value={amount}
        onChange={e => setAmount(e.target.value)}
      />
      <button type="submit">支付</button>
    </form>
  );
}

关键点:

  • useState 返回一个数组:[当前值, 更新函数]
  • 更新状态会触发组件重新渲染
  • 状态更新是异步的,不能依赖当前值计算下一个值
jsx 复制代码
// ❌ 错误:依赖当前值
setCount(count + 1);
setCount(count + 1); // 可能只加 1

// ✅ 正确:使用函数式更新
setCount(prev => prev + 1);
setCount(prev => prev + 1); // 会加 2

3.2 useEffect:副作用处理

useEffect 用于处理副作用(数据请求、订阅、DOM 操作等)。

jsx 复制代码
import { useState, useEffect } from 'react';

function Home() {
  const [repo, setRepo] = useState(null);
  const token = useAuthStore(state => state.token);

  // 依赖 token,token 变化时重新执行
  useEffect(() => {
    if (!token) return; // 无 token 不请求
    
    (async () => {
      const res = await getRepo();
      if (res.code === 0) {
        setRepo(res.data);
      }
    })();
  }, [token]); // 依赖数组:token 变化时执行

  return <div>{repo && <pre>{JSON.stringify(repo)}</pre>}</div>;
}

关键点:

  • useEffect 在组件渲染后执行
  • 第二个参数是依赖数组 ,决定何时重新执行
    • []:只在组件挂载时执行一次
    • [token]:token 变化时执行
    • 不传:每次渲染都执行(慎用)
  • 可以返回一个清理函数,在组件卸载或下次执行前调用
jsx 复制代码
useEffect(() => {
  const timer = setInterval(() => {
    console.log('每秒执行');
  }, 1000);

  // 清理函数:组件卸载时清除定时器
  return () => clearInterval(timer);
}, []);

3.3 表单处理:受控组件

React 中表单元素的状态由组件控制,称为受控组件

jsx 复制代码
function Login() {
  const [formData, setFormData] = useState({ username: '', password: '' });

  const handleChange = e => {
    const { name, value } = e.target;
    // 使用计算属性名更新对应字段
    setFormData(prev => ({ ...prev, [name]: value }));
  };

  return (
    <form>
      <input
        name="username"
        value={formData.username}
        onChange={handleChange}
      />
      <input
        name="password"
        type="password"
        value={formData.password}
        onChange={handleChange}
      />
    </form>
  );
}

关键点:

  • 表单元素的 value 由 React 状态控制
  • onChange 事件更新状态
  • 使用 name 属性统一处理多个字段

四、路由系统:React Router

4.1 基础路由配置

React Router 是 React 的标准路由库。

jsx 复制代码
import { BrowserRouter as Router, Routes, Route } from 'react-router-dom';
import { lazy, Suspense } from 'react';

// 懒加载组件
const Home = lazy(() => import('./pages/Home'));
const Login = lazy(() => import('./pages/Login'));
const Pay = lazy(() => import('./pages/Pay'));

function App() {
  return (
    <Router>
      <Suspense fallback={<div>loading...</div>}>
        <Routes>
          <Route path="/" element={<Home />} />
          <Route path="/login" element={<Login />} />
          <Route path="/pay" element={<Pay />} />
        </Routes>
      </Suspense>
    </Router>
  );
}

关键点:

  • Router:路由容器,提供路由上下文
  • Routes:路由集合
  • Route:单个路由规则,path 匹配 URL,element 渲染组件
  • lazy:懒加载组件,按需加载,提升性能
  • Suspense:懒加载时的占位 UI

4.2 路由导航

使用 useNavigate 进行编程式导航。

jsx 复制代码
import { useNavigate, useLocation } from 'react-router-dom';

function Login() {
  const navigate = useNavigate();
  const location = useLocation();
  const from = location.state?.from || '/'; // 获取来源路径

  const handleLogin = async () => {
    // ... 登录逻辑
    navigate(from, { replace: true }); // 登录成功后跳转
  };

  return <button onClick={handleLogin}>登录</button>;
}

关键点:

  • useNavigate:返回导航函数
  • useLocation:返回当前路由信息
  • location.state:可以传递状态数据
  • replace: true:替换当前历史记录(用户不能返回)

4.3 路由守卫:保护页面

路由守卫用于保护需要登录才能访问的页面。

jsx 复制代码
import { Navigate, useLocation } from 'react-router-dom';
import { useAuthStore } from '../store/user';

function RequireAuth({ children }) {
  const token = useAuthStore(state => state.token);
  const location = useLocation();

  if (!token) {
    // 未登录,重定向到登录页,并保存来源路径
    return <Navigate to="/login" replace state={{ from: location.pathname }} />;
  }

  return children; // 已登录,渲染子组件
}

// 使用
<Route path="/pay" element={
  <RequireAuth>
    <Pay />
  </RequireAuth>
} />

关键点:

  • RequireAuth 是一个高阶组件,包裹需要保护的页面
  • 检查 token,无 token 则重定向
  • 通过 state 传递来源路径,登录后跳回

五、状态管理:Zustand 全局状态

5.1 为什么需要全局状态?

组件内部状态(useState)只能在组件内部使用。但有些状态需要跨组件共享,比如:

  • 用户登录信息(token、user)
  • 主题设置(深色/浅色)
  • 购物车数据

传统方案是 Context API ,但代码较繁琐。Zustand 是一个轻量级替代方案。

5.2 Zustand 基础用法

jsx 复制代码
import { create } from 'zustand';

// 创建 store
export const useAuthStore = create(set => ({
  // 状态
  token: localStorage.getItem('token') || '',
  user: JSON.parse(localStorage.getItem('user')) || null,
  
  // 动作(修改状态的方法)
  setAuth: ({ token, user }) => {
    localStorage.setItem('token', token);
    localStorage.setItem('user', JSON.stringify(user));
    set({ token, user }); // 更新状态
  },
  
  logout: () => {
    localStorage.removeItem('token');
    localStorage.removeItem('user');
    set({ token: '', user: null });
  }
}));

5.3 在组件中使用

jsx 复制代码
function Nav() {
  // 选择性订阅状态
  const token = useAuthStore(state => state.token);
  const user = useAuthStore(state => state.user);
  const logout = useAuthStore(state => state.logout);

  return (
    <nav>
      {user && <span>{user.username}</span>}
      {token && <button onClick={logout}>退出</button>}
    </nav>
  );
}

function Login() {
  const setAuth = useAuthStore(state => state.setAuth);

  const handleLogin = async () => {
    const res = await login(formData);
    if (res.code === 0) {
      setAuth({ token: res.token, user: res.user }); // 更新全局状态
    }
  };
}

关键点:

  • create 创建 store,返回一个 Hook
  • 状态和动作在同一个对象中定义
  • 组件通过 Hook 订阅状态,只有订阅的状态变化才会重新渲染
  • 可以持久化到 localStorage,刷新后恢复

5.4 Zustand vs Context

特性 Zustand Context
代码量
性能 自动优化 需要手动优化
学习曲线
适用场景 中小型项目 大型项目

六、API 层:Axios 封装

6.1 创建 Axios 实例

jsx 复制代码
import axios from 'axios';

const instance = axios.create({
  baseURL: '/api', // 基础 URL
  timeout: 5000    // 超时时间
});

关键点:

  • axios.create 创建独立实例,避免全局配置污染
  • baseURL:所有请求会自动拼接
  • timeout:请求超时时间

6.2 请求拦截器:自动加 Token

jsx 复制代码
instance.interceptors.request.use(config => {
  const token = localStorage.getItem('token');
  if (token) {
    config.headers['Authorization'] = `Bearer ${token}`;
  }
  return config;
});

关键点:

  • 拦截器在每个请求发出前执行
  • 自动从 localStorage 读取 token
  • 添加到请求头的 Authorization 字段

6.3 响应拦截器:统一处理

jsx 复制代码
instance.interceptors.response.use(res => {
  return res.data; // 直接返回数据,无需 res.data
});

关键点:

  • 响应拦截器在收到响应后执行
  • 统一返回 res.data,简化调用方代码

6.4 封装 API 方法

jsx 复制代码
// api/user.js
import axios from './config';

export const login = async (data) => {
  const res = await axios.post('/login', data);
  return res;
};

// api/repo.js
import axios from './config';

export const getRepo = async () => {
  const res = await axios.get('/repo');
  return res;
};

关键点:

  • 每个 API 方法独立文件
  • 统一使用封装的 axios 实例
  • 返回 Promise,调用方使用 await

七、JWT 鉴权:完整流程

7.1 什么是 JWT?

JWT(JSON Web Token)是一种用于身份验证的开放标准。它由三部分组成:

  1. Header:算法和类型
  2. Payload:用户数据(JSON)
  3. Signature:签名(防篡改)

7.2 登录流程

markdown 复制代码
1. 用户提交表单(username + password)
   ↓
2. 调用 /api/login
   ↓
3. Mock 验证账号密码
   ↓
4. 签发 JWT:jwt.sign({ user, role }, secret)
   ↓
5. 返回 token 给前端
   ↓
6. 前端存储到 localStorage + Zustand

7.3 请求受保护资源

markdown 复制代码
1. 前端请求 /api/repo
   ↓
2. Axios 拦截器自动加 Authorization: Bearer xxx
   ↓
3. Mock 从请求头提取 token
   ↓
4. 验证 token:jwt.verify(token, secret)
   ↓
5. 返回用户数据

7.4 Mock 实现

jsx 复制代码
import jwt from 'jsonwebtoken';
const secret = 'secret819!$';

export default [
  {
    url: '/api/login',
    method: 'post',
    response: (req) => {
      const { username, password } = req.body;
      
      if (username !== 'admin' || password !== '123456') {
        return { code: -1, message: '账号或密码错误' };
      }
      
      // 签发 JWT
      const token = jwt.sign(
        { user: username, role: 'admin' },
        secret,
        { expiresIn: 86400 } // 24小时过期
      );
      
      return {
        code: 0,
        user: { username },
        token
      };
    }
  },
  {
    url: '/api/repo',
    method: 'get',
    response: (req) => {
      try {
        // 从请求头提取 token
        const token = req.headers['authorization'].split(' ')[1];
        
        // 验证 token
        const decoded = jwt.verify(token, secret);
        
        return {
          code: 0,
          data: decoded.user
        };
      } catch {
        return {
          code: 401,
          msg: 'Invalid token'
        };
      }
    }
  }
];

八、最佳实践

8.1 项目结构

bash 复制代码
src/
├── api/          # API 层(与后端交互)
├── components/   # 公共组件(跨页面复用)
├── pages/        # 页面组件(对应路由)
├── store/        # 全局状态
├── utils/        # 工具函数
└── App.jsx       # 路由配置

关键点:

  • 按功能模块划分目录
  • 组件命名用 PascalCase
  • 文件命名用 kebab-case 或 PascalCase

8.2 组件设计原则

  1. 单一职责:一个组件只做一件事
  2. 可复用性:通过 Props 使组件可复用
  3. 状态提升:多个组件共享的状态提升到父组件
  4. 全局状态:跨多个组件共享的状态使用 Zustand

8.3 性能优化

  1. 懒加载 :使用 lazy + Suspense 按需加载组件
  2. 选择性订阅:Zustand 只订阅需要的状态
  3. 避免不必要的渲染 :使用 React.memo 包裹纯展示组件
jsx 复制代码
import { memo } from 'react';

const Nav = memo(function Nav() {
  // 只有 props 或订阅的状态变化才会重新渲染
  return <nav>...</nav>;
});

8.4 错误处理

jsx 复制代码
const handleLogin = async () => {
  try {
    const res = await login(formData);
    if (res.code === 0) {
      setAuth({ token: res.token, user: res.user });
      navigate(from, { replace: true });
    } else {
      alert(res.message || '登录失败');
    }
  } catch (err) {
    console.error(err);
    alert('登录失败');
  }
};

关键点:

  • 使用 try-catch 捕获异步错误
  • 检查响应码,区分业务错误和系统错误
  • 给用户友好的错误提示

九、总结

通过这个项目,你学到了:

  1. React 基础:组件、JSX、Props、状态管理
  2. 路由系统:React Router、懒加载、路由守卫
  3. 状态管理:useState、useEffect、Zustand
  4. API 层:Axios 封装、拦截器、JWT 鉴权
  5. 工程实践:项目结构、代码组织、最佳实践

核心概念回顾

  • 组件:React 应用的基本单元
  • 状态:驱动 UI 更新的数据
  • 路由:管理页面导航
  • 拦截器:统一处理请求/响应
  • JWT:无状态的身份验证方案

下一步

  • 学习 TypeScript + React
  • 学习服务端渲染(Next.js)
  • 学习单元测试(Jest + React Testing Library)
  • 学习状态管理进阶(Redux Toolkit)

附录:运行项目

bash 复制代码
cd login-demo
pnpm install
pnpm dev

测试账号:admin / 123456

访问:

  • / - 首页(登录后显示受保护资源)
  • /login - 登录页
  • /pay - 支付页(需要登录)

作者 :基于实际项目整理

日期:2026-09-10

相关推荐
光影少年18 小时前
如何实现RN 多环境、多渠道打包
前端·react native·react.js
云上工程笔记1 天前
AstraFlow + React/Vite:如何用自然语言开发并部署一个 AI 咖啡网站原型
前端·人工智能·react.js
光影少年1 天前
React18 对RN 的影响
android·前端·react.js·ios·前端框架
xiaoyan20151 天前
2026最新款Electron41+React19+AntDesign电脑端后台管理系统Exe
react.js·electron·vite
用户298698530141 天前
在 React 中使用 JavaScript 将 HTML 转换为图片
javascript·react.js·html
科里 Coralyx1 天前
CoT Toolformer ReAct:工具调用、推理链与 Agent 的起源
前端·react.js·前端框架
走到天涯海角2 天前
react里面的长列表渲染优化
前端·react.js·前端框架
晴天162 天前
React 版本迭代解析:从15到19 核心差异-Day40
前端·react.js·前端框架