React 前后端交互技术详解
1. Promise 对象
1.1 Promise 简介
Promise 是 JavaScript 中用于处理异步操作的对象,它代表一个异步操作的最终完成(或失败)及其结果值。
1.2 Promise 的原理
Promise 有三种状态:
- pending:初始状态,既不是成功,也不是失败状态
- fulfilled:操作成功完成
- rejected:操作失败
状态一旦改变,就不会再变。
1.3 Promise 的使用方法
创建 Promise
javascript
// 创建 Promise 对象
const myPromise = new Promise((resolve, reject) => {
// 异步操作
const success = true; // 模拟操作结果
if (success) {
resolve("操作成功!"); // 成功时调用 resolve
} else {
reject("操作失败!"); // 失败时调用 reject
}
});
Promise 的基本使用
javascript
// 使用 then、catch 处理 Promise
myPromise
.then((result) => {
console.log("成功:", result); // 处理成功结果
})
.catch((error) => {
console.log("失败:", error); // 处理错误
})
.finally(() => {
console.log("操作完成"); // 无论成功失败都会执行
});
Promise 链式调用
javascript
// 模拟异步函数
function asyncTask1() {
return new Promise((resolve) => {
setTimeout(() => resolve("任务1完成"), 1000);
});
}
function asyncTask2(data) {
return new Promise((resolve) => {
setTimeout(() => resolve(`${data} -> 任务2完成`), 1000);
});
}
function asyncTask3(data) {
return new Promise((resolve, reject) => {
setTimeout(() => {
const success = Math.random() > 0.5;
if (success) {
resolve(`${data} -> 任务3完成`);
} else {
reject("任务3失败");
}
}, 1000);
});
}
// 链式调用
asyncTask1()
.then(result => {
console.log(result);
return asyncTask2(result); // 返回新的 Promise
})
.then(result => {
console.log(result);
return asyncTask3(result);
})
.then(result => {
console.log("最终结果:", result);
})
.catch(error => {
console.error("链式调用出错:", error);
});
Promise 静态方法
javascript
// Promise.all - 所有 Promise 都成功时返回结果数组
const promise1 = Promise.resolve(3);
const promise2 = 42;
const promise3 = new Promise((resolve) => {
setTimeout(resolve, 100, 'foo');
});
Promise.all([promise1, promise2, promise3])
.then((values) => {
console.log(values); // [3, 42, "foo"]
});
// Promise.race - 第一个完成的 Promise 的结果
Promise.race([promise1, promise3])
.then((value) => {
console.log(value); // 3(promise1 立即完成)
});
// Promise.allSettled - 所有 Promise 完成后返回状态和结果
Promise.allSettled([promise1, Promise.reject("错误")])
.then((results) => {
console.log(results);
// [{status: "fulfilled", value: 3}, {status: "rejected", reason: "错误"}]
});
2. HTTP 客户端 - Axios
2.1 Axios 简介
Axios 是一个基于 Promise 的 HTTP 客户端,用于浏览器和 Node.js。
2.2 安装和引入
bash
npm install axios
javascript
import axios from 'axios';
2.3 Axios 基本使用
GET 请求
javascript
// 基本的 GET 请求
axios.get('https://jsonplaceholder.typicode.com/posts')
.then(response => {
console.log('响应数据:', response.data);
console.log('状态码:', response.status);
console.log('状态文本:', response.statusText);
console.log('响应头:', response.headers);
})
.catch(error => {
console.error('请求失败:', error);
});
// 带参数的 GET 请求
axios.get('https://jsonplaceholder.typicode.com/posts', {
params: {
userId: 1,
_limit: 5
}
})
.then(response => {
console.log('用户1的前5篇文章:', response.data);
});
// 使用 async/await
async function fetchPosts() {
try {
const response = await axios.get('https://jsonplaceholder.typicode.com/posts/1');
console.log('文章详情:', response.data);
} catch (error) {
console.error('获取文章失败:', error);
}
}
POST 请求
javascript
// 创建新文章
const newPost = {
title: '新的文章标题',
body: '这是文章内容',
userId: 1
};
axios.post('https://jsonplaceholder.typicode.com/posts', newPost)
.then(response => {
console.log('创建成功:', response.data);
})
.catch(error => {
console.error('创建失败:', error);
});
// 带配置的 POST 请求
axios.post('https://jsonplaceholder.typicode.com/posts', newPost, {
headers: {
'Content-Type': 'application/json',
'Authorization': 'Bearer your-token'
},
timeout: 5000 // 5秒超时
})
.then(response => {
console.log('响应:', response);
});
PUT 和 DELETE 请求
javascript
// PUT 请求 - 更新资源
const updatedPost = {
id: 1,
title: '更新后的标题',
body: '更新后的内容',
userId: 1
};
axios.put('https://jsonplaceholder.typicode.com/posts/1', updatedPost)
.then(response => {
console.log('更新成功:', response.data);
});
// DELETE 请求 - 删除资源
axios.delete('https://jsonplaceholder.typicode.com/posts/1')
.then(response => {
console.log('删除成功:', response.data);
});
2.4 Axios 配置
全局配置
javascript
// 设置全局配置
axios.defaults.baseURL = 'https://jsonplaceholder.typicode.com';
axios.defaults.timeout = 10000;
axios.defaults.headers.common['Authorization'] = 'Bearer your-token';
axios.defaults.headers.post['Content-Type'] = 'application/json';
// 现在可以直接使用相对路径
axios.get('/posts')
.then(response => {
console.log(response.data);
});
创建实例配置
javascript
// 创建自定义 Axios 实例
const apiClient = axios.create({
baseURL: 'https://api.example.com',
timeout: 10000,
headers: {
'Content-Type': 'application/json'
}
});
// 添加请求拦截器
apiClient.interceptors.request.use(
(config) => {
// 在发送请求之前做些什么
const token = localStorage.getItem('token');
if (token) {
config.headers.Authorization = `Bearer ${token}`;
}
console.log('发送请求:', config);
return config;
},
(error) => {
// 对请求错误做些什么
return Promise.reject(error);
}
);
// 添加响应拦截器
apiClient.interceptors.response.use(
(response) => {
// 对响应数据做点什么
console.log('收到响应:', response);
return response;
},
(error) => {
// 对响应错误做点什么
if (error.response.status === 401) {
// 处理未授权错误
localStorage.removeItem('token');
window.location.href = '/login';
}
return Promise.reject(error);
}
);
并发请求
javascript
// 同时发送多个请求
function getUserAndPosts(userId) {
return axios.all([
axios.get(`/users/${userId}`),
axios.get(`/posts?userId=${userId}`)
]);
}
getUserAndPosts(1)
.then(axios.spread((userResponse, postsResponse) => {
console.log('用户信息:', userResponse.data);
console.log('用户文章:', postsResponse.data);
}))
.catch(error => {
console.error('获取数据失败:', error);
});
3. React 中的前后端交互
3.1 在 React 组件中使用 Axios
jsx
import React, { useState, useEffect } from 'react';
import axios from 'axios';
function PostsComponent() {
const [posts, setPosts] = useState([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
// 组件挂载时获取数据
useEffect(() => {
const fetchPosts = async () => {
try {
setLoading(true);
const response = await axios.get('https://jsonplaceholder.typicode.com/posts');
setPosts(response.data);
setError(null);
} catch (err) {
setError('获取文章失败: ' + err.message);
setPosts([]);
} finally {
setLoading(false);
}
};
fetchPosts();
}, []); // 空依赖数组表示只在组件挂载时执行
// 创建新文章
const createPost = async (postData) => {
try {
const response = await axios.post('https://jsonplaceholder.typicode.com/posts', postData);
setPosts(prevPosts => [response.data, ...prevPosts]);
return response.data;
} catch (err) {
setError('创建文章失败: ' + err.message);
throw err;
}
};
// 删除文章
const deletePost = async (postId) => {
try {
await axios.delete(`https://jsonplaceholder.typicode.com/posts/${postId}`);
setPosts(prevPosts => prevPosts.filter(post => post.id !== postId));
} catch (err) {
setError('删除文章失败: ' + err.message);
}
};
if (loading) return <div>加载中...</div>;
if (error) return <div>错误: {error}</div>;
return (
<div>
<h1>文章列表</h1>
<button onClick={() => createPost({ title: '新文章', body: '内容', userId: 1 })}>
创建新文章
</button>
{posts.map(post => (
<div key={post.id} style={{ border: '1px solid #ccc', margin: '10px', padding: '10px' }}>
<h3>{post.title}</h3>
<p>{post.body}</p>
<button onClick={() => deletePost(post.id)}>删除</button>
</div>
))}
</div>
);
}
export default PostsComponent;
3.2 自定义 Hook 封装 API 调用
jsx
import { useState, useEffect } from 'react';
import axios from 'axios';
// 自定义 Hook 用于 API 调用
function useApi(url, options = {}) {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
const fetchData = async () => {
try {
setLoading(true);
setError(null);
const response = await axios(url, options);
setData(response.data);
} catch (err) {
setError(err.message);
} finally {
setLoading(false);
}
};
useEffect(() => {
fetchData();
}, [url]); // 当 URL 变化时重新获取数据
return { data, loading, error, refetch: fetchData };
}
// 使用自定义 Hook 的组件
function UserProfile({ userId }) {
const { data: user, loading, error, refetch } = useApi(
`https://jsonplaceholder.typicode.com/users/${userId}`
);
if (loading) return <div>加载用户信息...</div>;
if (error) return <div>错误: {error} <button onClick={refetch}>重试</button></div>;
return (
<div>
<h2>{user.name}</h2>
<p>邮箱: {user.email}</p>
<p>电话: {user.phone}</p>
</div>
);
}
4. 综合案例
4.1 完整的博客应用示例
jsx
import React, { useState, useEffect } from 'react';
import axios from 'axios';
// 配置 Axios 实例
const api = axios.create({
baseURL: 'https://jsonplaceholder.typicode.com',
timeout: 10000,
});
// 博客应用主组件
function BlogApp() {
const [posts, setPosts] = useState([]);
const [users, setUsers] = useState([]);
const [selectedUser, setSelectedUser] = useState('');
const [newPost, setNewPost] = useState({ title: '', body: '' });
const [loading, setLoading] = useState(false);
// 获取所有用户
useEffect(() => {
const fetchUsers = async () => {
try {
const response = await api.get('/users');
setUsers(response.data);
} catch (error) {
console.error('获取用户失败:', error);
}
};
fetchUsers();
}, []);
// 根据选择的用户获取文章
useEffect(() => {
if (selectedUser) {
fetchUserPosts(selectedUser);
}
}, [selectedUser]);
const fetchUserPosts = async (userId) => {
setLoading(true);
try {
const response = await api.get(`/posts?userId=${userId}`);
setPosts(response.data);
} catch (error) {
console.error('获取文章失败:', error);
} finally {
setLoading(false);
}
};
const handleCreatePost = async (e) => {
e.preventDefault();
if (!newPost.title || !newPost.body || !selectedUser) {
alert('请填写完整信息');
return;
}
try {
const postData = {
...newPost,
userId: parseInt(selectedUser)
};
const response = await api.post('/posts', postData);
// 模拟添加新文章(JSONPlaceholder 不会真正创建)
setPosts(prevPosts => [response.data, ...prevPosts]);
setNewPost({ title: '', body: '' });
alert('文章创建成功!');
} catch (error) {
console.error('创建文章失败:', error);
alert('创建文章失败');
}
};
const handleDeletePost = async (postId) => {
if (!window.confirm('确定要删除这篇文章吗?')) return;
try {
await api.delete(`/posts/${postId}`);
setPosts(prevPosts => prevPosts.filter(post => post.id !== postId));
alert('文章删除成功!');
} catch (error) {
console.error('删除文章失败:', error);
alert('删除文章失败');
}
};
return (
<div style={{ padding: '20px', maxWidth: '800px', margin: '0 auto' }}>
<h1>博客管理系统</h1>
{/* 用户选择 */}
<div style={{ marginBottom: '20px' }}>
<label>选择用户: </label>
<select
value={selectedUser}
onChange={(e) => setSelectedUser(e.target.value)}
style={{ marginLeft: '10px', padding: '5px' }}
>
<option value="">请选择用户</option>
{users.map(user => (
<option key={user.id} value={user.id}>
{user.name}
</option>
))}
</select>
</div>
{/* 创建新文章表单 */}
<form onSubmit={handleCreatePost} style={{ marginBottom: '30px', padding: '20px', border: '1px solid #ddd' }}>
<h3>创建新文章</h3>
<div style={{ marginBottom: '10px' }}>
<input
type="text"
placeholder="文章标题"
value={newPost.title}
onChange={(e) => setNewPost({...newPost, title: e.target.value})}
style={{ width: '100%', padding: '8px', marginBottom: '10px' }}
/>
</div>
<div style={{ marginBottom: '10px' }}>
<textarea
placeholder="文章内容"
value={newPost.body}
onChange={(e) => setNewPost({...newPost, body: e.target.value})}
style={{ width: '100%', padding: '8px', height: '100px', marginBottom: '10px' }}
/>
</div>
<button type="submit" disabled={!selectedUser}>
发布文章
</button>
</form>
{/* 文章列表 */}
<div>
<h2>文章列表 {selectedUser && `- 用户${selectedUser}`}</h2>
{loading ? (
<div>加载中...</div>
) : (
<div>
{posts.length === 0 ? (
<div>暂无文章</div>
) : (
posts.map(post => (
<div key={post.id} style={{
border: '1px solid #ccc',
margin: '10px 0',
padding: '15px',
borderRadius: '5px'
}}>
<h3>{post.title}</h3>
<p>{post.body}</p>
<div style={{ marginTop: '10px' }}>
<button
onClick={() => handleDeletePost(post.id)}
style={{
backgroundColor: '#ff4444',
color: 'white',
border: 'none',
padding: '5px 10px',
borderRadius: '3px',
cursor: 'pointer'
}}
>
删除
</button>
</div>
</div>
))
)}
</div>
)}
</div>
</div>
);
}
export default BlogApp;
4.2 错误处理和加载状态优化
jsx
import React from 'react';
// 高阶组件:处理加载和错误状态
function withApiStatus(WrappedComponent) {
return function EnhancedComponent({ loading, error, ...props }) {
if (loading) {
return (
<div style={{ textAlign: 'center', padding: '20px' }}>
<div>加载中...</div>
</div>
);
}
if (error) {
return (
<div style={{ textAlign: 'center', padding: '20px', color: 'red' }}>
<div>错误: {error}</div>
<button onClick={props.onRetry}>重试</button>
</div>
);
}
return <WrappedComponent {...props} />;
};
}
// 使用高阶组件的示例
const PostListWithStatus = withApiStatus(({ posts, onDelete }) => (
<div>
{posts.map(post => (
<div key={post.id} style={{ border: '1px solid #ccc', margin: '10px', padding: '10px' }}>
<h3>{post.title}</h3>
<p>{post.body}</p>
<button onClick={() => onDelete(post.id)}>删除</button>
</div>
))}
</div>
));
5. 最佳实践和注意事项
5.1 错误处理最佳实践
javascript
// 统一的错误处理函数
function handleApiError(error) {
if (error.response) {
// 服务器响应了错误状态码
switch (error.response.status) {
case 401:
// 未授权,跳转到登录页
window.location.href = '/login';
break;
case 403:
// 禁止访问
alert('没有权限执行此操作');
break;
case 404:
// 资源不存在
alert('请求的资源不存在');
break;
case 500:
// 服务器内部错误
alert('服务器内部错误,请稍后重试');
break;
default:
alert(`请求失败: ${error.response.status}`);
}
} else if (error.request) {
// 请求已发出但没有收到响应
alert('网络错误,请检查网络连接');
} else {
// 其他错误
alert(`请求配置错误: ${error.message}`);
}
// 记录错误日志
console.error('API Error:', error);
}
5.2 取消请求
javascript
import React, { useEffect, useState } from 'react';
import axios from 'axios';
function SearchComponent() {
const [results, setResults] = useState([]);
const [query, setQuery] = useState('');
useEffect(() => {
// 创建取消令牌
const source = axios.CancelToken.source();
const search = async () => {
if (!query) {
setResults([]);
return;
}
try {
const response = await axios.get(`/api/search?q=${query}`, {
cancelToken: source.token
});
setResults(response.data);
} catch (error) {
if (!axios.isCancel(error)) {
console.error('搜索失败:', error);
}
}
};
// 防抖搜索
const timeoutId = setTimeout(search, 300);
// 清理函数:取消未完成的请求
return () => {
clearTimeout(timeoutId);
source.cancel('取消之前的搜索请求');
};
}, [query]);
return (
<div>
<input
type="text"
value={query}
onChange={(e) => setQuery(e.target.value)}
placeholder="搜索..."
/>
<ul>
{results.map(result => (
<li key={result.id}>{result.name}</li>
))}
</ul>
</div>
);
}
这份详细的指南涵盖了 React 前后端交互的核心概念和实践,从基础的 Promise 到复杂的 Axios 配置和 React 集成,提供了完整的代码示例和最佳实践。