React API集成与路由
一、API集成
1. Fetch API
javascript
import { useState, useEffect } from 'react';
function ApiWithFetch() {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(false);
const [error, setError] = useState(null);
// GET请求
const fetchUsers = async () => {
setLoading(true);
setError(null);
try {
const response = await fetch('https://jsonplaceholder.typicode.com/users', {
method: 'GET',
headers: {
'Content-Type': 'application/json',
'Authorization': 'Bearer your-token-here' // 如果有的话
}
});
if (!response.ok) {
throw new Error(`HTTP错误: ${response.status}`);
}
const result = await response.json();
setData(result);
} catch (err) {
setError(err.message);
} finally {
setLoading(false);
}
};
// POST请求
const createUser = async (userData) => {
try {
const response = await fetch('https://jsonplaceholder.typicode.com/users', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(userData)
});
if (!response.ok) {
throw new Error('创建失败');
}
const newUser = await response.json();
return newUser;
} catch (err) {
console.error('创建用户失败:', err);
throw err;
}
};
// PUT/PATCH请求
const updateUser = async (id, updates) => {
try {
const response = await fetch(`https://jsonplaceholder.typicode.com/users/${id}`, {
method: 'PUT', // 或 'PATCH'
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(updates)
});
if (!response.ok) {
throw new Error('更新失败');
}
return await response.json();
} catch (err) {
console.error('更新用户失败:', err);
throw err;
}
};
// DELETE请求
const deleteUser = async (id) => {
try {
const response = await fetch(`https://jsonplaceholder.typicode.com/users/${id}`, {
method: 'DELETE',
});
if (!response.ok) {
throw new Error('删除失败');
}
return true;
} catch (err) {
console.error('删除用户失败:', err);
throw err;
}
};
useEffect(() => {
fetchUsers();
}, []);
if (loading) return <div>加载中...</div>;
if (error) return <div>错误: {error}</div>;
return (
<div>
<h1>用户列表</h1>
{data && (
<ul>
{data.map(user => (
<li key={user.id}>
{user.name} - {user.email}
</li>
))}
</ul>
)}
</div>
);
}
2. Axios(推荐)
javascript
# 安装axios
npm install axios
javascript
import { useState, useEffect } from 'react';
import axios from 'axios';
// 创建axios实例
const api = axios.create({
baseURL: 'https://jsonplaceholder.typicode.com',
timeout: 10000,
headers: {
'Content-Type': 'application/json',
}
});
// 请求拦截器
api.interceptors.request.use(
(config) => {
// 添加token等
const token = localStorage.getItem('token');
if (token) {
config.headers.Authorization = `Bearer ${token}`;
}
return config;
},
(error) => {
return Promise.reject(error);
}
);
// 响应拦截器
api.interceptors.response.use(
(response) => response.data,
(error) => {
if (error.response) {
// 服务器返回错误状态码
switch (error.response.status) {
case 401:
// 未授权,跳转登录
window.location.href = '/login';
break;
case 403:
// 权限不足
console.error('权限不足');
break;
case 404:
console.error('资源不存在');
break;
case 500:
console.error('服务器错误');
break;
default:
console.error('请求失败');
}
} else if (error.request) {
// 请求已发出但没有响应
console.error('网络错误,请检查网络连接');
} else {
// 请求配置错误
console.error('请求配置错误:', error.message);
}
return Promise.reject(error);
}
);
function ApiWithAxios() {
const [users, setUsers] = useState([]);
const [loading, setLoading] = useState(false);
const [error, setError] = useState(null);
// GET请求
const fetchUsers = async () => {
setLoading(true);
setError(null);
try {
const response = await api.get('/users');
setUsers(response);
} catch (err) {
setError(err.message);
} finally {
setLoading(false);
}
};
// POST请求
const createUser = async (userData) => {
try {
const response = await api.post('/users', userData);
return response;
} catch (err) {
console.error('创建失败:', err);
throw err;
}
};
// 并发请求
const fetchMultipleData = async () => {
try {
const [users, posts] = await Promise.all([
api.get('/users'),
api.get('/posts')
]);
return { users: users.data, posts: posts.data };
} catch (err) {
console.error('并发请求失败:', err);
throw err;
}
};
// 带取消请求
const fetchWithCancel = async () => {
const source = axios.CancelToken.source();
try {
const response = await api.get('/users', {
cancelToken: source.token
});
return response.data;
} catch (err) {
if (axios.isCancel(err)) {
console.log('请求被取消:', err.message);
} else {
throw err;
}
}
// 取消请求
return () => {
source.cancel('组件卸载,取消请求');
};
};
useEffect(() => {
fetchUsers();
}, []);
if (loading) return <div>加载中...</div>;
if (error) return <div>错误: {error}</div>;
return (
<div>
<h1>用户列表 (Axios)</h1>
<ul>
{users.map(user => (
<li key={user.id}>
{user.name} - {user.email}
</li>
))}
</ul>
</div>
);
}
3. React Query(强烈推荐)
javascript
# 安装React Query
npm install @tanstack/react-query
javascript
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
import { api } from './api'; // 你的axios实例
function UsersWithReactQuery() {
const queryClient = useQueryClient();
// 获取用户列表
const {
data: users,
isLoading,
error,
refetch
} = useQuery({
queryKey: ['users'], // 查询键
queryFn: () => api.get('/users'),
staleTime: 5 * 60 * 1000, // 5分钟内数据不过期
cacheTime: 10 * 60 * 1000, // 10分钟后清除缓存
retry: 3, // 失败重试3次
retryDelay: (attemptIndex) => Math.min(1000 * 2 ** attemptIndex, 30000),
});
// 创建用户
const createUserMutation = useMutation({
mutationFn: (userData) => api.post('/users', userData),
onSuccess: () => {
// 用户创建成功后,使users查询无效,触发重新获取
queryClient.invalidateQueries({ queryKey: ['users'] });
},
onError: (error) => {
console.error('创建用户失败:', error);
}
});
// 更新用户
const updateUserMutation = useMutation({
mutationFn: ({ id, ...updates }) => api.put(`/users/${id}`, updates),
onSuccess: (updatedUser) => {
// 乐观更新:立即更新UI,不用等重新获取
queryClient.setQueryData(['users'], (oldUsers) =>
oldUsers.map(user =>
user.id === updatedUser.id ? updatedUser : user
)
);
}
});
// 删除用户
const deleteUserMutation = useMutation({
mutationFn: (id) => api.delete(`/users/${id}`),
onMutate: async (id) => {
// 取消正在进行的查询
await queryClient.cancelQueries({ queryKey: ['users'] });
// 保存之前的用户列表
const previousUsers = queryClient.getQueryData(['users']);
// 乐观更新
queryClient.setQueryData(['users'], (oldUsers) =>
oldUsers.filter(user => user.id !== id)
);
return { previousUsers };
},
onError: (err, id, context) => {
// 出错时回滚
queryClient.setQueryData(['users'], context.previousUsers);
},
onSettled: () => {
// 完成后重新获取
queryClient.invalidateQueries({ queryKey: ['users'] });
}
});
if (isLoading) return <div>加载中...</div>;
if (error) return <div>错误: {error.message}</div>;
return (
<div>
<h1>用户列表 (React Query)</h1>
<button onClick={() => refetch()}>刷新</button>
<button onClick={() =>
createUserMutation.mutate({
name: '新用户',
email: 'new@example.com'
})
}>
添加用户
</button>
{users?.map(user => (
<div key={user.id} style={{ margin: '10px 0', padding: '10px', border: '1px solid #ddd' }}>
<h3>{user.name}</h3>
<p>{user.email}</p>
<button onClick={() =>
updateUserMutation.mutate({
id: user.id,
name: `${user.name} (已更新)`
})
}>
更新
</button>
<button onClick={() => deleteUserMutation.mutate(user.id)}>
删除
</button>
</div>
))}
</div>
);
}
4. 自定义API Hook
javascript
// hooks/useApi.js
import { useState, useCallback } from 'react';
import { api } from '../services/api';
export function useApi() {
const [loading, setLoading] = useState(false);
const [error, setError] = useState(null);
const [data, setData] = useState(null);
const request = useCallback(async (config) => {
setLoading(true);
setError(null);
try {
const response = await api({
method: config.method || 'GET',
url: config.url,
data: config.data,
params: config.params
});
setData(response);
return response;
} catch (err) {
setError(err.response?.data?.message || err.message);
throw err;
} finally {
setLoading(false);
}
}, []);
const get = useCallback((url, params) =>
request({ method: 'GET', url, params }), [request]);
const post = useCallback((url, data) =>
request({ method: 'POST', url, data }), [request]);
const put = useCallback((url, data) =>
request({ method: 'PUT', url, data }), [request]);
const del = useCallback((url) =>
request({ method: 'DELETE', url }), [request]);
return {
loading,
error,
data,
request,
get,
post,
put,
delete: del,
setData,
setError,
clear: () => {
setError(null);
setData(null);
}
};
}
// 使用示例
function ApiHookExample() {
const {
loading,
error,
data: users,
get: fetchUsers,
post: createUser,
put: updateUser,
delete: deleteUser
} = useApi();
const handleFetch = async () => {
await fetchUsers('/users');
};
const handleCreate = async () => {
await createUser('/users', {
name: '新用户',
email: 'new@example.com'
});
handleFetch(); // 重新获取
};
if (loading) return <div>加载中...</div>;
if (error) return <div>错误: {error}</div>;
return (
<div>
<button onClick={handleFetch}>获取用户</button>
<button onClick={handleCreate}>创建用户</button>
{users && (
<ul>
{users.map(user => (
<li key={user.id}>
{user.name}
<button onClick={() => deleteUser(`/users/${user.id}`)}>删除</button>
</li>
))}
</ul>
)}
</div>
);
}
二、路由(React Router)
1. 基础路由配置
# 安装React Router
npm install react-router-dom
javascript
// App.jsx
import { BrowserRouter, Routes, Route, Link } from 'react-router-dom';
import { Home, About, Users, UserDetail, Dashboard, Settings, NotFound } from './pages';
function App() {
return (
<BrowserRouter>
<div className="app">
{/* 导航栏 */}
<nav>
<ul>
<li><Link to="/">首页</Link></li>
<li><Link to="/about">关于</Link></li>
<li><Link to="/users">用户</Link></li>
<li><Link to="/dashboard">仪表盘</Link></li>
</ul>
</nav>
{/* 路由配置 */}
<Routes>
<Route path="/" element={<Home />} />
<Route path="/about" element={<About />} />
<Route path="/users" element={<Users />} />
<Route path="/users/:id" element={<UserDetail />} />
<Route path="/dashboard/*" element={<DashboardLayout />} />
<Route path="*" element={<NotFound />} />
</Routes>
</div>
</BrowserRouter>
);
}
// 嵌套路由布局组件
function DashboardLayout() {
return (
<div className="dashboard-layout">
<aside>
<h3>仪表盘菜单</h3>
<ul>
<li><Link to="/dashboard">概览</Link></li>
<li><Link to="/dashboard/settings">设置</Link></li>
<li><Link to="/dashboard/analytics">分析</Link></li>
</ul>
</aside>
<main>
<Routes>
<Route index element={<Dashboard />} />
<Route path="settings" element={<Settings />} />
<Route path="analytics" element={<Analytics />} />
</Routes>
</main>
</div>
);
}
2. 动态路由与参数
javascript
// pages/UserDetail.jsx
import { useParams, useNavigate, useLocation, useSearchParams } from 'react-router-dom';
function UserDetail() {
// 获取路由参数
const { id } = useParams();
// 编程式导航
const navigate = useNavigate();
// 获取location对象
const location = useLocation();
// 获取查询参数
const [searchParams, setSearchParams] = useSearchParams();
const tab = searchParams.get('tab') || 'info';
const page = searchParams.get('page') || '1';
const handleEdit = () => {
// 导航到编辑页面
navigate(`/users/${id}/edit`, {
state: { from: location.pathname }, // 传递状态
replace: false // 替换当前记录
});
};
const handleGoBack = () => {
// 返回上一页
navigate(-1);
};
const changeTab = (tabName) => {
// 更新查询参数
setSearchParams({ tab: tabName, page });
};
return (
<div>
<h1>用户详情 ID: {id}</h1>
<div>
<button onClick={handleEdit}>编辑用户</button>
<button onClick={handleGoBack}>返回</button>
</div>
{/* Tab切换 */}
<div>
<button onClick={() => changeTab('info')}>基本信息</button>
<button onClick={() => changeTab('posts')}>帖子</button>
<button onClick={() => changeTab('comments')}>评论</button>
</div>
{/* 根据tab显示不同内容 */}
{tab === 'info' && <UserInfo userId={id} />}
{tab === 'posts' && <UserPosts userId={id} />}
{tab === 'comments' && <UserComments userId={id} />}
</div>
);
}
3. 路由守卫(认证保护)
javascript
// components/PrivateRoute.jsx
import { Navigate, useLocation } from 'react-router-dom';
import { useAuth } from '../contexts/AuthContext';
function PrivateRoute({ children, roles = [] }) {
const { isAuthenticated, user, loading } = useAuth();
const location = useLocation();
if (loading) {
return <div>检查权限...</div>;
}
if (!isAuthenticated) {
// 未登录,重定向到登录页
return <Navigate to="/login" state={{ from: location }} replace />;
}
if (roles.length > 0 && !roles.includes(user.role)) {
// 无权限,重定向到无权限页面
return <Navigate to="/unauthorized" replace />;
}
return children;
}
// 使用示例
function AppRoutes() {
return (
<Routes>
{/* 公开路由 */}
<Route path="/" element={<Home />} />
<Route path="/login" element={<Login />} />
{/* 需要登录的路由 */}
<Route path="/dashboard" element={
<PrivateRoute>
<Dashboard />
</PrivateRoute>
} />
{/* 需要管理员权限的路由 */}
<Route path="/admin" element={
<PrivateRoute roles={['admin', 'superadmin']}>
<AdminPanel />
</PrivateRoute>
} />
{/* 404页面 */}
<Route path="*" element={<NotFound />} />
</Routes>
);
}
4. 懒加载路由
javascript
import { Suspense, lazy } from 'react';
import { Routes, Route } from 'react-router-dom';
// 懒加载组件
const Home = lazy(() => import('./pages/Home'));
const About = lazy(() => import('./pages/About'));
const Users = lazy(() => import('./pages/Users'));
const UserDetail = lazy(() => import('./pages/UserDetail'));
// 加载中组件
const Loading = () => <div>加载中...</div>;
function App() {
return (
<BrowserRouter>
<Suspense fallback={<Loading />}>
<Routes>
<Route path="/" element={<Home />} />
<Route path="/about" element={<About />} />
<Route path="/users" element={<Users />} />
<Route path="/users/:id" element={<UserDetail />} />
</Routes>
</Suspense>
</BrowserRouter>
);
}
5. 路由配置集中管理
javascript
// routes/index.js
import { lazy } from 'react';
// 懒加载页面组件
const Home = lazy(() => import('../pages/Home'));
const About = lazy(() => import('../pages/About'));
const Users = lazy(() => import('../pages/Users'));
const UserDetail = lazy(() => import('../pages/UserDetail'));
const Login = lazy(() => import('../pages/Login'));
const Dashboard = lazy(() => import('../pages/Dashboard'));
const Admin = lazy(() => import('../pages/Admin'));
const NotFound = lazy(() => import('../pages/NotFound'));
// 路由配置
export const routes = [
{
path: '/',
element: <Home />,
meta: {
title: '首页',
requiresAuth: false
}
},
{
path: '/about',
element: <About />,
meta: {
title: '关于我们',
requiresAuth: false
}
},
{
path: '/login',
element: <Login />,
meta: {
title: '登录',
requiresAuth: false
}
},
{
path: '/users',
element: <Users />,
meta: {
title: '用户列表',
requiresAuth: true
}
},
{
path: '/users/:id',
element: <UserDetail />,
meta: {
title: '用户详情',
requiresAuth: true
}
},
{
path: '/dashboard',
element: <Dashboard />,
meta: {
title: '仪表盘',
requiresAuth: true,
roles: ['user', 'admin']
}
},
{
path: '/admin',
element: <Admin />,
meta: {
title: '管理后台',
requiresAuth: true,
roles: ['admin']
}
},
{
path: '*',
element: <NotFound />,
meta: {
title: '页面不存在',
requiresAuth: false
}
}
];
// 路由渲染组件
export function renderRoutes(routes) {
return (
<Routes>
{routes.map((route, index) => (
<Route
key={index}
path={route.path}
element={
<RouteGuard
element={route.element}
requiresAuth={route.meta?.requiresAuth}
roles={route.meta?.roles}
/>
}
/>
))}
</Routes>
);
}
三、完整实战:博客系统
javascript
// App.jsx
import { BrowserRouter, Routes, Route, Navigate } from 'react-router-dom';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import { ReactQueryDevtools } from '@tanstack/react-query-devtools';
import Layout from './components/Layout';
import Home from './pages/Home';
import Posts from './pages/Posts';
import PostDetail from './pages/PostDetail';
import CreatePost from './pages/CreatePost';
import EditPost from './pages/EditPost';
import Login from './pages/Login';
import Register from './pages/Register';
import Profile from './pages/Profile';
import Admin from './pages/Admin';
import NotFound from './pages/NotFound';
import { AuthProvider } from './contexts/AuthContext';
import PrivateRoute from './components/PrivateRoute';
// 创建React Query客户端
const queryClient = new QueryClient({
defaultOptions: {
queries: {
staleTime: 5 * 60 * 1000, // 5分钟
cacheTime: 10 * 60 * 1000, // 10分钟
retry: 2,
refetchOnWindowFocus: false,
},
},
});
function App() {
return (
<QueryClientProvider client={queryClient}>
<AuthProvider>
<BrowserRouter>
<Routes>
{/* 公开路由 */}
<Route path="/login" element={<Login />} />
<Route path="/register" element={<Register />} />
{/* 需要布局的受保护路由 */}
<Route element={<PrivateRoute><Layout /></PrivateRoute>}>
<Route path="/" element={<Home />} />
<Route path="/posts" element={<Posts />} />
<Route path="/posts/:id" element={<PostDetail />} />
<Route path="/posts/create" element={
<PrivateRoute roles={['admin', 'author']}>
<CreatePost />
</PrivateRoute>
} />
<Route path="/posts/:id/edit" element={
<PrivateRoute roles={['admin', 'author']}>
<EditPost />
</PrivateRoute>
} />
<Route path="/profile" element={<Profile />} />
<Route path="/admin" element={
<PrivateRoute roles={['admin']}>
<Admin />
</PrivateRoute>
} />
</Route>
{/* 404 */}
<Route path="*" element={<NotFound />} />
</Routes>
</BrowserRouter>
</AuthProvider>
{/* 开发工具 */}
<ReactQueryDevtools initialIsOpen={false} />
</QueryClientProvider>
);
}
javascript
// pages/Posts.jsx
import { useQuery } from '@tanstack/react-query';
import { Link, useSearchParams } from 'react-router-dom';
import { api } from '../services/api';
import Pagination from '../components/Pagination';
import Loading from '../components/Loading';
import Error from '../components/Error';
function Posts() {
const [searchParams, setSearchParams] = useSearchParams();
const page = parseInt(searchParams.get('page') || '1');
const limit = parseInt(searchParams.get('limit') || '10');
const search = searchParams.get('search') || '';
const { data, isLoading, error, isError } = useQuery({
queryKey: ['posts', { page, limit, search }],
queryFn: () =>
api.get('/posts', {
params: {
_page: page,
_limit: limit,
q: search
}
}),
});
const handleSearch = (e) => {
e.preventDefault();
const formData = new FormData(e.target);
const searchValue = formData.get('search');
setSearchParams({ page: '1', search: searchValue });
};
if (isLoading) return <Loading />;
if (isError) return <Error message={error.message} />;
return (
<div className="posts-page">
<div className="page-header">
<h1>文章列表</h1>
<Link to="/posts/create" className="btn btn-primary">
新建文章
</Link>
</div>
{/* 搜索 */}
<form onSubmit={handleSearch} className="search-form">
<input
type="text"
name="search"
defaultValue={search}
placeholder="搜索文章..."
/>
<button type="submit">搜索</button>
</form>
{/* 文章列表 */}
<div className="posts-list">
{data?.map(post => (
<div key={post.id} className="post-card">
<h3>
<Link to={`/posts/${post.id}`}>{post.title}</Link>
</h3>
<p>{post.body.substring(0, 100)}...</p>
<div className="post-meta">
<span>作者: {post.author?.name || '未知'}</span>
<span>日期: {new Date(post.createdAt).toLocaleDateString()}</span>
<span>阅读: {post.views || 0}</span>
</div>
<div className="post-actions">
<Link to={`/posts/${post.id}`} className="btn btn-sm btn-info">
查看
</Link>
<Link to={`/posts/${post.id}/edit`} className="btn btn-sm btn-warning">
编辑
</Link>
</div>
</div>
))}
</div>
{/* 分页 */}
{data?.length > 0 && (
<Pagination
currentPage={page}
totalPages={Math.ceil(data.total / limit)}
onPageChange={(newPage) =>
setSearchParams({ page: newPage.toString(), search })
}
/>
)}
</div>
);
}
javascript
// pages/PostDetail.jsx
import { useParams, useNavigate } from 'react-router-dom';
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
import { api } from '../services/api';
import { useAuth } from '../contexts/AuthContext';
import Loading from '../components/Loading';
import Error from '../components/Error';
function PostDetail() {
const { id } = useParams();
const navigate = useNavigate();
const queryClient = useQueryClient();
const { user } = useAuth();
// 获取文章详情
const { data: post, isLoading, isError, error } = useQuery({
queryKey: ['post', id],
queryFn: () => api.get(`/posts/${id}`),
});
// 获取评论
const { data: comments } = useQuery({
queryKey: ['comments', id],
queryFn: () => api.get(`/posts/${id}/comments`),
});
// 删除文章
const deleteMutation = useMutation({
mutationFn: () => api.delete(`/posts/${id}`),
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['posts'] });
navigate('/posts');
}
});
// 添加评论
const addCommentMutation = useMutation({
mutationFn: (comment) => api.post(`/posts/${id}/comments`, comment),
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['comments', id] });
}
});
const handleAddComment = async (e) => {
e.preventDefault();
const formData = new FormData(e.target);
const content = formData.get('content');
if (!content.trim()) return;
await addCommentMutation.mutate({
content,
authorId: user.id,
createdAt: new Date().toISOString()
});
e.target.reset();
};
if (isLoading) return <Loading />;
if (isError) return <Error message={error.message} />;
if (!post) return <div>文章不存在</div>;
const canEdit = user && (user.role === 'admin' || post.authorId === user.id);
return (
<div className="post-detail">
<article>
<h1>{post.title}</h1>
<div className="post-meta">
<span>作者: {post.author?.name}</span>
<span>发布日期: {new Date(post.createdAt).toLocaleDateString()}</span>
<span>阅读量: {post.views}</span>
</div>
<div className="post-content">
{post.content}
</div>
{/* 操作按钮 */}
<div className="post-actions">
{canEdit && (
<>
<button
className="btn btn-primary"
onClick={() => navigate(`/posts/${id}/edit`)}
>
编辑
</button>
<button
className="btn btn-danger"
onClick={() => {
if (window.confirm('确定要删除这篇文章吗?')) {
deleteMutation.mutate();
}
}}
disabled={deleteMutation.isLoading}
>
{deleteMutation.isLoading ? '删除中...' : '删除'}
</button>
</>
)}
<button
className="btn btn-secondary"
onClick={() => navigate(-1)}
>
返回
</button>
</div>
</article>
{/* 评论区域 */}
<section className="comments">
<h3>评论 ({comments?.length || 0})</h3>
{/* 添加评论表单 */}
{user && (
<form onSubmit={handleAddComment} className="add-comment-form">
<textarea
name="content"
placeholder="写下你的评论..."
rows="3"
required
/>
<button
type="submit"
disabled={addCommentMutation.isLoading}
>
{addCommentMutation.isLoading ? '提交中...' : '提交评论'}
</button>
</form>
)}
{/* 评论列表 */}
<div className="comments-list">
{comments?.map(comment => (
<div key={comment.id} className="comment">
<div className="comment-header">
<strong>{comment.author?.name}</strong>
<span>{new Date(comment.createdAt).toLocaleString()}</span>
</div>
<div className="comment-content">
{comment.content}
</div>
</div>
))}
</div>
</section>
</div>
);
}
四、API和路由最佳实践
1. 项目结构
javascript
src/
├── services/
│ ├── api.js # axios实例和配置
│ ├── auth.js # 认证相关API
│ ├── posts.js # 文章相关API
│ └── users.js # 用户相关API
├── hooks/
│ ├── useApi.js # API自定义Hook
│ ├── useAuth.js # 认证Hook
│ └── useQuery.js # 查询Hook
├── contexts/
│ └── AuthContext.js # 认证Context
├── pages/
│ ├── Home.jsx
│ ├── Posts.jsx
│ └── Profile.jsx
├── components/
│ ├── Layout/
│ ├── Loading/
│ └── Error/
├── routes/
│ └── index.js # 路由配置
└── utils/
└── constants.js
2. API服务层示例
javascript
// services/api.js
import axios from 'axios';
// 创建axios实例
const api = axios.create({
baseURL: process.env.REACT_APP_API_URL || 'http://localhost:3000/api',
timeout: 10000,
headers: {
'Content-Type': 'application/json',
},
});
// 请求拦截器
api.interceptors.request.use(
(config) => {
const token = localStorage.getItem('token');
if (token) {
config.headers.Authorization = `Bearer ${token}`;
}
return config;
},
(error) => {
return Promise.reject(error);
}
);
// 响应拦截器
api.interceptors.response.use(
(response) => response.data,
(error) => {
if (error.response?.status === 401) {
// token过期,清除本地存储
localStorage.removeItem('token');
localStorage.removeItem('user');
// 跳转到登录页
window.location.href = '/login';
}
return Promise.reject(error.response?.data || error.message);
}
);
// 封装常用方法
export const get = (url, params) => api.get(url, { params });
export const post = (url, data) => api.post(url, data);
export const put = (url, data) => api.put(url, data);
export const del = (url) => api.delete(url);
export const patch = (url, data) => api.patch(url, data);
export default api;
五、常见问题解决
1. 跨域问题
javascript
// 开发环境代理配置 (vite.config.js)
export default {
server: {
proxy: {
'/api': {
target: 'http://localhost:3000',
changeOrigin: true,
rewrite: (path) => path.replace(/^\/api/, '')
}
}
}
}
2. 请求取消
javascript
// 在组件卸载时取消请求
useEffect(() => {
const controller = new AbortController();
const fetchData = async () => {
try {
const response = await api.get('/data', {
signal: controller.signal
});
// 处理响应
} catch (err) {
if (err.name === 'AbortError') {
console.log('请求被取消');
} else {
// 处理其他错误
}
}
};
fetchData();
return () => {
controller.abort();
};
}, []);
3. 路由权限控制
javascript
// 基于角色的权限控制
export const checkPermission = (userRole, allowedRoles) => {
if (!allowedRoles || allowedRoles.length === 0) return true;
if (!userRole) return false;
return allowedRoles.includes(userRole);
};
关键要点
-
API集成:
-
使用axios处理HTTP请求
-
使用React Query管理服务器状态
-
封装自定义Hook复用逻辑
-
统一错误处理和拦截器
-
-
路由管理:
-
使用React Router v6
-
实现路由守卫保护页面
-
使用懒加载优化性能
-
合理组织路由结构
-
-
状态同步:
-
服务器状态用React Query
-
全局客户端状态用Context或Zustand
-
局部状态用useState/useReducer
-
-
错误处理:
-
统一的错误边界
-
友好的错误提示
-
请求重试机制
-
-
性能优化:
-
组件懒加载
-
请求防抖/节流
-
缓存策略优化
-