很多刚开始做 React 项目的人,会直接在组件里写:
js
axios.post('/api/login', {
username,
password
})
刚开始当然没问题。
但项目一大,就会出现一个很明显的问题:
页面代码开始和请求代码搅在一起。
登录页面既要负责输入框,又要负责接口地址,又要负责 Axios,还要处理服务器返回的数据。
所以这篇文章先不急着讲 JWT,我们先把一个最基础的问题搞清楚:
React 页面到底应该怎么和后端通信?
一、先想清楚:React 为什么需要 API 层?
假设现在有一个登录页面。
最直接的写法是:
js
async function handleLogin() {
const res = await axios.post('/api/login', {
username,
password
})
}
看起来很简单。
但以后项目里可能还有:
text
登录
获取用户信息
获取订单
获取仓库
创建订单
删除数据
如果每个组件都直接使用 Axios,就会变成:
text
Login.jsx → axios
Home.jsx → axios
Pay.jsx → axios
Profile.jsx → axios
Order.jsx → axios
请求逻辑散落在各种页面里。
所以我们希望把"调用接口"这件事情单独拿出来。
最终变成:
text
React 页面
↓
调用 API 方法
↓
API 文件负责请求
↓
Axios 发送 HTTP 请求
这样页面只关心:
"我要登录。"
而不需要关心:
"登录请求到底是 POST 还是 GET、地址是什么、Axios 怎么配置?"
二、创建 api 目录
在 src 下创建:
text
src/
└── api/
这个目录专门放前端调用后端接口的代码。
接下来我们先创建:
text
src/api/config.js
为什么先创建它?
因为所有接口都需要使用 Axios。
如果每个文件都自己创建一个 Axios 实例:
js
axios.create(...)
那么以后想统一修改:
- baseURL
- timeout
- 请求拦截器
- 响应拦截器
就要改很多文件。
所以先创建一个统一的 Axios 实例。
三、创建统一的 Axios 实例
在 src/api/config.js 中:
js
import axios from 'axios';
const instance = axios.create({
baseURL: '/api',
timeout: 5000
});
export default instance;
这里最重要的是:
js
baseURL: '/api'
以后我们写:
js
axios.post('/login')
实际请求的就是:
text
/api/login
而不是每次都写完整地址。
所以:
text
config.js
解决的是:
整个项目怎么统一使用 Axios。
它并不负责某个具体业务。
四、创建用户 API
现在我们需要处理登录。
在 src/api 下创建:
text
src/api/user.js
为什么还要创建 user.js?
因为 config.js 只负责 Axios 的公共配置。
而:
text
user.js
负责的是:
用户相关的接口。
例如:
- 登录
- 获取用户信息
- 修改用户信息
因此在 user.js 中:
js
import axios from './config';
export const login = async (data) => {
const res = await axios.post('/login', data);
return res.data;
}
现在我们有两个文件:
text
src/api/
├── config.js // 统一配置 Axios
└── user.js // 用户相关接口
这两个文件的职责已经不一样了。
五、页面不再直接使用 Axios
登录页面以后不需要写:
js
axios.post('/api/login')
而是:
js
import { login } from '../api/user';
登录时:
js
const res = await login(formData);
这时候,整个过程就非常清楚:
text
用户填写账号密码
↓
Login 页面调用 login()
↓
user.js 负责发送 /login 请求
↓
config.js 提供 Axios 实例
↓
Axios 发出请求
页面负责"做什么"。
API 层负责"怎么请求"。
这就是最基础的前端分层。
六、可是现在还有一个问题:后端在哪里?
我们现在已经写了:
js
axios.post('/login')
但谁来接这个请求?
如果我们真的写一个后端,还需要:
- Node.js
- Express
- 路由
- 数据库
- 服务器
对于学习前端来说,这些内容有时候反而会把重点带偏。
所以这个 demo 使用了:
Mock
Mock 的目的很简单:
先假装我们已经有一个后端。
七、使用 Vite Mock 模拟后端
项目根目录创建:
text
mock/
然后创建:
text
mock/user.js
同时在 Vite 中配置 Mock 插件。
js
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
import { viteMockServe } from 'vite-plugin-mock'
export default defineConfig({
plugins: [
react(),
viteMockServe({
mockPath: 'mock',
localEnable: true
})
],
})
这里:
js
mockPath: 'mock'
告诉 Vite:
Mock 接口就在
mock目录里。
八、创建第一个 Mock 登录接口
在:
text
mock/user.js
中:
js
export default [
{
url: '/api/login',
method: 'post',
response: req => {
const body = req.body || {};
if (
body.username !== 'admin' ||
body.password !== '123456'
) {
return {
code: -1,
message: 'username or password error'
};
}
return {
code: 0,
user: {
username: body.username
}
};
}
}
]
现在,浏览器请求:
text
POST /api/login
Mock 就会接住。
于是整个过程变成:
text
用户填写账号密码
↓
Login 页面调用 login()
↓
user.js 发送 /login
↓
Axios 处理请求
↓
Vite Mock 接收 /api/login
↓
mock/user.js 验证账号密码
↓
返回 JSON
这时候你应该已经能理解:
Mock 不是前端 API。
它是在开发阶段模拟后端。
而 API 层则是:
前端为了调用后端而做的代码组织。
这是两个完全不同的东西。
九、为什么现在还不能说"登录系统完成了"?
因为我们现在只能做到:
text
用户名密码正确
↓
服务器返回成功
但用户下一次访问:
text
/api/repo
服务器怎么知道:
"这个人刚刚已经登录过了?"
这就是下一篇要解决的问题。