最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

React API集成與路由的最佳實(shí)踐

 更新時間:2026年02月02日 10:14:55   作者:比特森林探險記  
本文給大家介紹了React API集成與路由的最佳實(shí)踐,本文結(jié)合實(shí)例代碼給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友參考下吧

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('創(chuàng)建失敗');
      }
      const newUser = await response.json();
      return newUser;
    } catch (err) {
      console.error('創(chuàng)建用戶失敗:', 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';
// 創(chuàng)建axios實(shí)例
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);
  }
);
// 響應(yīng)攔截器
api.interceptors.response.use(
  (response) => response.data,
  (error) => {
    if (error.response) {
      // 服務(wù)器返回錯誤狀態(tài)碼
      switch (error.response.status) {
        case 401:
          // 未授權(quán),跳轉(zhuǎn)登錄
          window.location.href = '/login';
          break;
        case 403:
          // 權(quán)限不足
          console.error('權(quán)限不足');
          break;
        case 404:
          console.error('資源不存在');
          break;
        case 500:
          console.error('服務(wù)器錯誤');
          break;
        default:
          console.error('請求失敗');
      }
    } else if (error.request) {
      // 請求已發(fā)出但沒有響應(yīng)
      console.error('網(wǎng)絡(luò)錯誤,請檢查網(wǎng)絡(luò)連接');
    } 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('創(chuàng)建失敗:', err);
      throw err;
    }
  };
  // 并發(fā)請求
  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('并發(fā)請求失敗:', 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(強(qiáng)烈推薦)

# 安裝React Query
npm install @tanstack/react-query
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
import { api } from './api'; // 你的axios實(shí)例
function UsersWithReactQuery() {
  const queryClient = useQueryClient();
  // 獲取用戶列表
  const { 
    data: users, 
    isLoading, 
    error, 
    refetch 
  } = useQuery({
    queryKey: ['users'], // 查詢鍵
    queryFn: () => api.get('/users'),
    staleTime: 5 * 60 * 1000, // 5分鐘內(nèi)數(shù)據(jù)不過期
    cacheTime: 10 * 60 * 1000, // 10分鐘后清除緩存
    retry: 3, // 失敗重試3次
    retryDelay: (attemptIndex) => Math.min(1000 * 2 ** attemptIndex, 30000),
  });
  // 創(chuàng)建用戶
  const createUserMutation = useMutation({
    mutationFn: (userData) => api.post('/users', userData),
    onSuccess: () => {
      // 用戶創(chuàng)建成功后,使users查詢無效,觸發(fā)重新獲取
      queryClient.invalidateQueries({ queryKey: ['users'] });
    },
    onError: (error) => {
      console.error('創(chuàng)建用戶失敗:', 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) => {
      // 取消正在進(jìn)行的查詢
      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}>創(chuàng)建用戶</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. 基礎(chǔ)路由配置

# 安裝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">
        {/* 導(dǎo)航欄 */}
        <nav>
          <ul>
            <li><Link to="/">首頁</Link></li>
            <li><Link to="/about">關(guān)于</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">設(shè)置</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. 動態(tài)路由與參數(shù)

// pages/UserDetail.jsx
import { useParams, useNavigate, useLocation, useSearchParams } from 'react-router-dom';
function UserDetail() {
  // 獲取路由參數(shù)
  const { id } = useParams();
  // 編程式導(dǎo)航
  const navigate = useNavigate();
  // 獲取location對象
  const location = useLocation();
  // 獲取查詢參數(shù)
  const [searchParams, setSearchParams] = useSearchParams();
  const tab = searchParams.get('tab') || 'info';
  const page = searchParams.get('page') || '1';
  const handleEdit = () => {
    // 導(dǎo)航到編輯頁面
    navigate(`/users/${id}/edit`, { 
      state: { from: location.pathname }, // 傳遞狀態(tài)
      replace: false // 替換當(dāng)前記錄
    });
  };
  const handleGoBack = () => {
    // 返回上一頁
    navigate(-1);
  };
  const changeTab = (tabName) => {
    // 更新查詢參數(shù)
    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>
      {/* 根據(jù)tab顯示不同內(nèi)容 */}
      {tab === 'info' && <UserInfo userId={id} />}
      {tab === 'posts' && <UserPosts userId={id} />}
      {tab === 'comments' && <UserComments userId={id} />}
    </div>
  );
}

3. 路由守衛(wèi)(認(rèn)證保護(hù))

// 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>檢查權(quán)限...</div>;
  }
  if (!isAuthenticated) {
    // 未登錄,重定向到登錄頁
    return <Navigate to="/login" state={{ from: location }} replace />;
  }
  if (roles.length > 0 && !roles.includes(user.role)) {
    // 無權(quán)限,重定向到無權(quán)限頁面
    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>
      } />
      {/* 需要管理員權(quán)限的路由 */}
      <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: '關(guān)于我們',
      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>
  );
}

三、完整實(shí)戰(zhàn):博客系統(tǒng)

// 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';
// 創(chuàng)建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 />} />
            {/* 需要布局的受保護(hù)路由 */}
            <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>
      {/* 開發(fā)工具 */}
      <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>發(fā)布日期: {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>
      {/* 評論區(qū)域 */}
      <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和路由最佳實(shí)踐

1. 項(xiàng)目結(jié)構(gòu)

src/
├── services/
│   ├── api.js          # axios實(shí)例和配置
│   ├── auth.js         # 認(rèn)證相關(guān)API
│   ├── posts.js        # 文章相關(guān)API
│   └── users.js        # 用戶相關(guān)API
├── hooks/
│   ├── useApi.js       # API自定義Hook
│   ├── useAuth.js      # 認(rèn)證Hook
│   └── useQuery.js     # 查詢Hook
├── contexts/
│   └── AuthContext.js  # 認(rèn)證Context
├── pages/
│   ├── Home.jsx
│   ├── Posts.jsx
│   └── Profile.jsx
├── components/
│   ├── Layout/
│   ├── Loading/
│   └── Error/
├── routes/
│   └── index.js       # 路由配置
└── utils/
    └── constants.js

2. API服務(wù)層示例

// services/api.js
import axios from 'axios';
// 創(chuàng)建axios實(shí)例
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);
  }
);
// 響應(yīng)攔截器
api.interceptors.response.use(
  (response) => response.data,
  (error) => {
    if (error.response?.status === 401) {
      // token過期,清除本地存儲
      localStorage.removeItem('token');
      localStorage.removeItem('user');
      // 跳轉(zhuǎn)到登錄頁
      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. 跨域問題

// 開發(fā)環(huán)境代理配置 (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
      });
      // 處理響應(yīng)
    } catch (err) {
      if (err.name === 'AbortError') {
        console.log('請求被取消');
      } else {
        // 處理其他錯誤
      }
    }
  };
  fetchData();
  return () => {
    controller.abort();
  };
}, []);

3. 路由權(quán)限控制

// 基于角色的權(quán)限控制
export const checkPermission = (userRole, allowedRoles) => {
  if (!allowedRoles || allowedRoles.length === 0) return true;
  if (!userRole) return false;
  return allowedRoles.includes(userRole);
};

關(guān)鍵要點(diǎn)

  • API集成
    • 使用axios處理HTTP請求
    • 使用React Query管理服務(wù)器狀態(tài)
    • 封裝自定義Hook復(fù)用邏輯
    • 統(tǒng)一錯誤處理和攔截器
  • 路由管理
    • 使用React Router v6
    • 實(shí)現(xiàn)路由守衛(wèi)保護(hù)頁面
    • 使用懶加載優(yōu)化性能
    • 合理組織路由結(jié)構(gòu)
  • 狀態(tài)同步
    • 服務(wù)器狀態(tài)用React Query
    • 全局客戶端狀態(tài)用Context或Zustand
    • 局部狀態(tài)用useState/useReducer
  • 錯誤處理
    • 統(tǒng)一的錯誤邊界
    • 友好的錯誤提示
    • 請求重試機(jī)制
  • 性能優(yōu)化
    • 組件懶加載
    • 請求防抖/節(jié)流
    • 緩存策略優(yōu)化

到此這篇關(guān)于React API集成與路由的最佳實(shí)踐的文章就介紹到這了,更多相關(guān)react api集成與路由內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • react build 后打包發(fā)布總結(jié)

    react build 后打包發(fā)布總結(jié)

    這篇文章主要介紹了react build 后打包發(fā)布總結(jié),小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2018-08-08
  • react+axios實(shí)現(xiàn)github搜索用戶功能(示例代碼)

    react+axios實(shí)現(xiàn)github搜索用戶功能(示例代碼)

    這篇文章主要介紹了react+axios實(shí)現(xiàn)搜索github用戶功能,本文給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下
    2021-09-09
  • React 錯誤邊界組件的處理

    React 錯誤邊界組件的處理

    這篇文章主要介紹了React 錯誤邊界組件的處理,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2021-04-04
  • react?component?function組件使用詳解

    react?component?function組件使用詳解

    這篇文章主要為大家介紹了react?component?function組件的使用示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2022-11-11
  • react項(xiàng)目使用json-server模擬接口獲取數(shù)據(jù)的操作方法

    react項(xiàng)目使用json-server模擬接口獲取數(shù)據(jù)的操作方法

    文章介紹了如何在React項(xiàng)目中使用json-server模擬接口并獲取數(shù)據(jù),首先安裝json-server,創(chuàng)建JSON格式的數(shù)據(jù)文件,并在package.json中添加啟動命令,啟動服務(wù)后,可以通過指定的路徑獲取模擬的數(shù)據(jù),感興趣的朋友一起看看吧
    2025-11-11
  • React實(shí)現(xiàn)文件分片上傳和下載的方法詳解

    React實(shí)現(xiàn)文件分片上傳和下載的方法詳解

    在當(dāng)今的前端開發(fā)中,處理文件流操作已經(jīng)成為一個常見的需求,無論是上傳、下載、讀取、展示還是其他的文件處理操作,都需要高效且可靠地處理二進(jìn)制數(shù)據(jù),本文將深入探討如何使用 React 實(shí)現(xiàn)文件分片上傳和下載,并介紹相關(guān)的基本概念和技術(shù),需要的朋友可以參考下
    2023-08-08
  • 在React中實(shí)現(xiàn)Vue的插槽功能的示例代碼

    在React中實(shí)現(xiàn)Vue的插槽功能的示例代碼

    在?Vue?中,插槽(Slots)允許父組件向子組件傳遞?HTML?結(jié)構(gòu),從而實(shí)現(xiàn)更靈活的組件復(fù)用,具名插槽允許父組件向子組件傳遞多個不同的?HTML?結(jié)構(gòu),在?React?中,我們沒有直接的插槽概念,但可以通過?props.children?和函數(shù)作為?props?來實(shí)現(xiàn)類似的功能
    2025-01-01
  • React基礎(chǔ)-JSX的本質(zhì)-虛擬DOM的創(chuàng)建過程實(shí)例分析

    React基礎(chǔ)-JSX的本質(zhì)-虛擬DOM的創(chuàng)建過程實(shí)例分析

    這篇文章主要介紹了React基礎(chǔ)-JSX的本質(zhì)-虛擬DOM的創(chuàng)建過程,結(jié)合具體實(shí)例形式分析了虛擬dom的基本原理與實(shí)現(xiàn)方法,需要的朋友可以參考下
    2023-05-05
  • 關(guān)于React動態(tài)加載路由處理的相關(guān)問題

    關(guān)于React動態(tài)加載路由處理的相關(guān)問題

    這篇文章主要介紹了關(guān)于React動態(tài)加載路由處理的相關(guān)問題,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2019-01-01
  • 詳解React native全局變量的使用(跨組件的通信)

    詳解React native全局變量的使用(跨組件的通信)

    本篇文章主要介紹了詳解React native全局變量的使用(跨組件的通信),具有一定的參考價值,有興趣的同學(xué)可以了解一下
    2017-09-09

最新評論

浙江省| 封丘县| 普定县| 和硕县| 文成县| 四子王旗| 武城县| 隆安县| 德惠市| 内黄县| 龙泉市| 淮南市| 景东| 龙泉市| 高尔夫| 江城| 彭山县| 镇安县| 德庆县| 曲沃县| 手游| 呼玛县| 郓城县| 丹寨县| 手机| 盘山县| 从化市| 陆良县| 黎城县| 特克斯县| 岳普湖县| 油尖旺区| 喀什市| 湛江市| 库伦旗| 罗源县| 庆元县| 克什克腾旗| 抚远县| 侯马市| 巫溪县|