React API集成与路由的最佳实践

 更新时间:2026年02月02日 10:14:55   作者:比特森林探险记  
本文给大家介绍了React API集成与路由的最佳实践,本文结合实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友参考下吧

React API集成与路由

一、API集成

1. Fetch API

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(推荐)

# 安装axios
npm install axios
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(强烈推荐)

# 安装React Query
npm install @tanstack/react-query
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

// 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
// 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. 动态路由与参数

// 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. 路由守卫(认证保护)

// 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. 懒加载路由

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. 路由配置集中管理

// 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>
  );
}

三、完整实战:博客系统

// 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>
  );
}
// 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>
  );
}
// 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. 项目结构

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服务层示例

// 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. 跨域问题

// 开发环境代理配置 (vite.config.js)
export default {
  server: {
    proxy: {
      '/api': {
        target: 'http://localhost:3000',
        changeOrigin: true,
        rewrite: (path) => path.replace(/^\/api/, '')
      }
    }
  }
}

2. 请求取消

// 在组件卸载时取消请求
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. 路由权限控制

// 基于角色的权限控制
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
  • 错误处理
    • 统一的错误边界
    • 友好的错误提示
    • 请求重试机制
  • 性能优化
    • 组件懒加载
    • 请求防抖/节流
    • 缓存策略优化

到此这篇关于React API集成与路由的最佳实践的文章就介绍到这了,更多相关react api集成与路由内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • 详解React中的不可变值

    详解React中的不可变值

    这篇文章主要介绍了React中的不可变值的相关资料,帮助大家更好的理解和学习使用react.js,感兴趣的朋友可以了解下
    2021-04-04
  • Vite+React搭建开发构建环境实践记录

    Vite+React搭建开发构建环境实践记录

    这篇文章主要介绍了Vite+React搭建开发构建环境实践,本文通过实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2022-09-09
  • React中的页面跳转方式示例详解

    React中的页面跳转方式示例详解

    React Router提供了几种不同的跳转方式,包括使用组件进行页面跳转、使用组件进行重定向,以及使用编程式导航进行跳转,这篇文章主要介绍了React中的页面跳转方式详解,需要的朋友可以参考下
    2023-09-09
  • React中事件的类型定义方式

    React中事件的类型定义方式

    这篇文章主要介绍了React中事件的类型定义方式,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2022-12-12
  • React的diff算法核心复用图文详解

    React的diff算法核心复用图文详解

    这篇文章主要为大家介绍了React的diff算法核心复用详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2022-08-08
  • React-Native之TextInput组件的设置以及如何获取输入框的内容

    React-Native之TextInput组件的设置以及如何获取输入框的内容

    这篇文章主要介绍了React-Native之TextInput组件的设置以及如何获取输入框的内容问题,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2023-05-05
  • React函数组件useContext useReducer自定义hooks

    React函数组件useContext useReducer自定义hooks

    这篇文章主要为大家介绍了React函数组件useContext useReducer自定义hooks示例详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2022-08-08
  • React如何接收excel文件下载导出功能封装

    React如何接收excel文件下载导出功能封装

    这篇文章主要介绍了React如何接收excel文件下载导出功能封装,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2022-09-09
  • 再次谈论React.js实现原生js拖拽效果引起的一系列问题

    再次谈论React.js实现原生js拖拽效果引起的一系列问题

    React 起源于 Facebook 的内部项目,因为该公司对市场上所有 JavaScript MVC 框架,都不满意,就决定自己写一套,用来架设 Instagram 的网站.本文给大家介绍React.js实现原生js拖拽效果,需要的朋友一起学习吧
    2016-04-04
  • 详解react-webpack2-热模块替换[HMR]

    详解react-webpack2-热模块替换[HMR]

    这篇文章主要介绍了详解react-webpack2-热模块替换[HMR], 小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2017-08-08

最新评论