React 与后台 API 接口交互
0x01 概述
React 与后台 API 交互是前端开发的核心能力。本文档涵盖从基础到进阶的 API 交互方式,包括原生 fetch、axios、React Query/SWR 等方案。
React API 交互核心功能点
flowchart TD
subgraph 组件生命周期
A[组件渲染] --> B[useEffect 触发]
B --> C{请求方式}
end
subgraph 状态管理
D[useState<br/>管理数据状态] --> E[loading<br/>加载状态]
D --> F[data<br/>数据存储]
D --> G[error<br/>错误处理]
end
subgraph 副作用处理
H[useEffect<br/>副作用执行] --> I[发起请求]
I --> J[Promise.then<br/>处理响应]
J --> K[setState<br/>更新UI]
K --> L[组件重渲染]
end
subgraph 自定义Hook封装
M[useFetch<br/>通用请求Hook] --> N[useCallback<br/>缓存请求函数]
M --> O[useRef<br/>AbortController]
M --> P[useState<br/>状态管理]
end
subgraph 进阶方案
Q[React Query<br/>服务状态管理] --> R[自动缓存]
Q --> S[乐观更新]
Q --> T[后台重新验证]
U[SWR] --> V[轮询]
U --> W[焦点重新验证]
end
C -->|基础| H
C -->|封装| M
C -->|进阶| Q
C -->|进阶| U
style A fill:#e1f5fe
style D fill:#e8f5e9
style H fill:#fff3e0
style M fill:#f3e5f5
style Q fill:#ffebee
核心 Hook 关系
sequenceDiagram
participant Component as 组件
participant useState as useState
participant useEffect as useEffect
participant API as API请求
Note over Component: 初始化状态
Component->>useState: setLoading(true)
useState-->>Component: 更新状态
Note over Component: 副作用触发
Component->>useEffect: 执行副作用
useEffect->>API: fetch/axios
Note over API: 等待响应...
API-->>useEffect: 返回数据
useEffect->>useState: setData(data)
useState-->>Component: 更新状态
useEffect->>useState: setLoading(false)
useState-->>Component: 更新状态
Note over Component: 重渲染显示数据
数据流说明
| 阶段 | 使用的 Hook | 作用 |
|---|---|---|
| 状态初始化 | useState |
存储 loading、data、error 状态 |
| 请求触发 | useEffect |
组件挂载时发起请求 |
| 请求取消 | useRef + AbortController |
组件卸载时取消请求 |
| 函数缓存 | useCallback |
缓存请求函数,避免重创建 |
| 数据缓存 | React Query/SWR |
缓存和重新验证数据 |
0x02 使用 fetch
基本 GET 请求
import { useState, useEffect } from 'react';
interface User {
id: number;
name: string;
email: string;
}
function UserList() {
const [users, setUsers] = useState<User[]>([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
useEffect(() => {
fetch('https://api.example.com/users')
.then(res => {
if (!res.ok) {
throw new Error(`HTTP error! status: ${res.status}`);
}
return res.json();
})
.then(data => {
setUsers(data);
setLoading(false);
})
.catch(err => {
setError(err.message);
setLoading(false);
});
}, []);
if (loading) return <p>Loading...</p>;
if (error) return <p>Error: {error}</p>;
return (
<ul>
{users.map(user => (
<li key={user.id}>{user.name} - {user.email}</li>
))}
</ul>
);
}
POST 请求
import { useState } from 'react';
function CreateUser() {
const [formData, setFormData] = useState({
name: '',
email: ''
});
const [status, setStatus] = useState<'idle' | 'loading' | 'success' | 'error'>('idle');
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
setStatus('loading');
try {
const response = await fetch('https://api.example.com/users', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${localStorage.getItem('token')}`
},
body: JSON.stringify(formData)
});
if (!response.ok) {
throw new Error('Failed to create user');
}
const data = await response.json();
console.log('Created user:', data);
setStatus('success');
setFormData({ name: '', email: '' });
} catch (error) {
console.error('Error:', error);
setStatus('error');
}
};
return (
<form onSubmit={handleSubmit}>
<input
value={formData.name}
onChange={e => setFormData({ ...formData, name: e.target.value })}
placeholder="Name"
/>
<input
value={formData.email}
onChange={e => setFormData({ ...formData, email: e.target.value })}
placeholder="Email"
/>
<button type="submit" disabled={status === 'loading'}>
{status === 'loading' ? 'Creating...' : 'Create'}
</button>
{status === 'success' && <p>User created successfully!</p>}
{status === 'error' && <p>Failed to create user</p>}
</form>
);
}
请求超时处理
import { useState, useEffect } from 'react';
function fetchWithTimeout(
url: string,
options: RequestInit = {},
timeout = 5000
): Promise<Response> {
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), timeout);
return fetch(url, {
...options,
signal: controller.signal
}).finally(() => clearTimeout(timeoutId));
}
function DataFetcher() {
const [data, setData] = useState(null);
const [error, setError] = useState<string | null>(null);
useEffect(() => {
fetchWithTimeout('https://api.example.com/data', {}, 3000)
.then(res => res.json())
.then(setData)
.catch(err => {
if (err.name === 'AbortError') {
setError('Request timeout');
} else {
setError(err.message);
}
});
}, []);
return <div>{data ? JSON.stringify(data) : error || 'Loading...'}</div>;
}
0x03 使用 axios
axios 实例配置
import axios from 'axios';
// 创建 axios 实例
const api = axios.create({
baseURL: 'https://api.example.com',
timeout: 10000,
headers: {
'Content-Type': 'application/json'
}
});
// 请求拦截器 - 添加 Token
api.interceptors.request.use(
config => {
const token = localStorage.getItem('token');
if (token) {
config.headers.Authorization = `Bearer ${token}`;
}
return config;
},
error => Promise.reject(error)
);
// 响应拦截器 - 统一错误处理
api.interceptors.response.use(
response => response,
error => {
if (error.response?.status === 401) {
// Token 过期,清除并跳转登录
localStorage.removeItem('token');
window.location.href = '/login';
}
return Promise.reject(error);
}
);
export default api;
封装 API 服务
import api from './api';
// 用户相关 API
export const userApi = {
// 获取用户列表
getList: (params?: { page?: number; limit?: number }) =>
api.get('/users', { params }),
// 获取单个用户
getById: (id: number) =>
api.get(`/users/${id}`),
// 创建用户
create: (data: { name: string; email: string }) =>
api.post('/users', data),
// 更新用户
update: (id: number, data: Partial<{ name: string; email: string }>) =>
api.put(`/users/${id}`, data),
// 删除用户
delete: (id: number) =>
api.delete(`/users/${id}`)
};
// 商品相关 API
export const productApi = {
list: (params?: { category?: string; page?: number }) =>
api.get('/products', { params }),
detail: (id: number) =>
api.get(`/products/${id}`),
create: (data: FormData) =>
api.post('/products', data, {
headers: { 'Content-Type': 'multipart/form-data' }
})
};
在组件中使用
import { useState, useEffect } from 'react';
import { userApi } from './api/services';
function UserList() {
const [users, setUsers] = useState<any[]>([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
useEffect(() => {
userApi.getList({ page: 1, limit: 10 })
.then(res => {
setUsers(res.data);
setLoading(false);
})
.catch(err => {
setError(err.message);
setLoading(false);
});
}, []);
if (loading) return <div>Loading...</div>;
if (error) return <div>Error: {error}</div>;
return (
<ul>
{users.map(user => (
<li key={user.id}>{user.name}</li>
))}
</ul>
);
}
function CreateUser() {
const [name, setName] = useState('');
const [email, setEmail] = useState('');
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
try {
await userApi.create({ name, email });
alert('User created!');
setName('');
setEmail('');
} catch (error) {
alert('Failed to create user');
}
};
return (
<form onSubmit={handleSubmit}>
<input value={name} onChange={e => setName(e.target.value)} />
<input value={email} onChange={e => setEmail(e.target.value)} />
<button type="submit">Create</button>
</form>
);
}
0x04 使用 React Query
React Query 是目前推荐的服务器状态管理方案。
基本配置
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
const queryClient = new QueryClient({
defaultOptions: {
queries: {
staleTime: 5 * 60 * 1000, // 5分钟内数据视为新鲜
cacheTime: 30 * 60 * 1000, // 缓存30分钟
retry: 3,
refetchOnWindowFocus: false
}
}
});
function App() {
return (
<QueryClientProvider client={queryClient}>
<YourApp />
</QueryClientProvider>
);
}
查询数据
import { useQuery } from '@tanstack/react-query';
interface User {
id: number;
name: string;
email: string;
}
function UserList() {
const {
data,
isLoading,
isError,
error,
refetch
} = useQuery({
queryKey: ['users'],
queryFn: async () => {
const res = await fetch('https://api.example.com/users');
if (!res.ok) throw new Error('Network response was not ok');
return res.json();
}
});
if (isLoading) return <div>Loading...</div>;
if (isError) return <div>Error: {error?.message}</div>;
return (
<div>
<button onClick={() => refetch()}>Refresh</button>
<ul>
{data?.map((user: User) => (
<li key={user.id}>{user.name} - {user.email}</li>
))}
</ul>
</div>
);
}
带参数查询
function UserDetail({ userId }: { userId: number }) {
const { data, isLoading, isError } = useQuery({
queryKey: ['user', userId],
queryFn: async () => {
const res = await fetch(`https://api.example.com/users/${userId}`);
return res.json();
},
enabled: !!userId // 只有 userId 存在时才执行
});
if (isLoading) return <div>Loading...</div>;
if (isError) return <div>Error loading user</div>;
return (
<div>
<h2>{data.name}</h2>
<p>{data.email}</p>
</div>
);
}
function UserListPage() {
const [selectedId, setSelectedId] = useState<number | null>(null);
return (
<div>
<UserList onSelect={setSelectedId} />
{selectedId && <UserDetail userId={selectedId} />}
</div>
);
}
mutations(增删改)
import { useMutation, useQueryClient } from '@tanstack/react-query';
function CreateUser() {
const queryClient = useQueryClient();
const [name, setName] = useState('');
const [email, setEmail] = useState('');
const mutation = useMutation({
mutationFn: (newUser: { name: string; email: string }) =>
fetch('https://api.example.com/users', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(newUser)
}).then(res => res.json()),
// 成功后刷新列表
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['users'] });
setName('');
setEmail('');
}
});
const handleSubmit = (e: React.FormEvent) => {
e.preventDefault();
mutation.mutate({ name, email });
};
return (
<form onSubmit={handleSubmit}>
<input value={name} onChange={e => setName(e.target.value)} />
<input value={email} onChange={e => setEmail(e.target.value)} />
<button
type="submit"
disabled={mutation.isPending}
>
{mutation.isPending ? 'Creating...' : 'Create'}
</button>
{mutation.isError && <p>Error creating user</p>}
{mutation.isSuccess && <p>User created!</p>}
</form>
);
}
function DeleteUser({ userId }: { userId: number }) {
const queryClient = useQueryClient();
const mutation = useMutation({
mutationFn: () =>
fetch(`https://api.example.com/users/${userId}`, {
method: 'DELETE'
}),
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['users'] });
}
});
return (
<button
onClick={() => mutation.mutate()}
disabled={mutation.isPending}
>
{mutation.isPending ? 'Deleting...' : 'Delete'}
</button>
);
}
乐观更新
import { useMutation, useQueryClient } from '@tanstack/react-query';
function UpdateUser({ user }: { user: User }) {
const queryClient = useQueryClient();
const mutation = useMutation({
mutationFn: (updatedUser: User) =>
fetch(`https://api.example.com/users/${user.id}`, {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(updatedUser)
}).then(res => res.json()),
// 乐观更新:先更新缓存,失败则回滚
onMutate: async (newUser: User) => {
// 取消正在进行的查询
await queryClient.cancelQueries({ queryKey: ['users'] });
// 保存之前的值
const previousUsers = queryClient.getQueryData(['users']);
// 立即更新缓存
queryClient.setQueryData(['users'], (old: User[] | undefined) =>
old?.map(u => u.id === newUser.id ? newUser : u)
);
return { previousUsers };
},
onError: (err, newUser, context) => {
// 失败回滚
queryClient.setQueryData(['users'], context?.previousUsers);
},
onSettled: () => {
// 无论成功失败都重新获取
queryClient.invalidateQueries({ queryKey: ['users'] });
}
});
return (
<button onClick={() => mutation.mutate({ ...user, name: 'Updated' })}>
Update
</button>
);
}
0x05 使用 SWR
SWR 是 Vercel 推出的轻量级数据获取库。
基本用法
import useSWR from 'swr';
const fetcher = (url: string) =>
fetch(url).then(res => res.json());
function UserList() {
const { data, error, isLoading, mutate } = useSWR('/api/users', fetcher);
if (error) return <div>Failed to load</div>;
if (isLoading) return <div>Loading...</div>;
return (
<div>
<button onClick={() => mutate()}>Refresh</button>
<ul>
{data?.map((user: User) => (
<li key={user.id}>{user.name}</li>
))}
</ul>
</div>
);
}
高级配置
function AdvancedSWR() {
const { data, isLoading } = useSWR('/api/data', fetcher, {
refreshInterval: 5000, // 每5秒轮询
revalidateOnFocus: true, // 窗口聚焦时重新验证
revalidateOnReconnect: true, // 重连时重新验证
dedupingInterval: 2000, // 2秒内不重复请求
shouldRetryOnError: true, // 错误重试
errorRetryInterval: 5000 // 错误重试间隔
});
return <div>{data ? JSON.stringify(data) : 'Loading...'}</div>;
}
0x06 自定义 Hook 封装
通用数据获取 Hook
import { useState, useEffect, useCallback } from 'react';
interface UseFetchOptions<T> {
url: string;
method?: 'GET' | 'POST' | 'PUT' | 'DELETE';
body?: any;
headers?: Record<string, string>;
}
interface UseFetchResult<T> {
data: T | null;
loading: boolean;
error: string | null;
refetch: () => Promise<void>;
}
function useFetch<T>(options: UseFetchOptions<T>): UseFetchResult<T> {
const [data, setData] = useState<T | null>(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
const fetchData = useCallback(async () => {
setLoading(true);
setError(null);
try {
const res = await fetch(options.url, {
method: options.method || 'GET',
headers: {
'Content-Type': 'application/json',
...options.headers
},
body: options.body ? JSON.stringify(options.body) : undefined
});
if (!res.ok) {
throw new Error(`Error: ${res.status}`);
}
const result = await res.json();
setData(result);
} catch (err: any) {
setError(err.message);
} finally {
setLoading(false);
}
}, [options.url, options.method, options.body, options.headers]);
useEffect(() => {
if (options.method === 'GET' || !options.method) {
fetchData();
}
}, [fetchData, options.method]);
return { data, loading, error, refetch: fetchData };
}
// 使用
function UserList() {
const { data, loading, error, refetch } = useFetch<User[]>({
url: 'https://api.example.com/users'
});
if (loading) return <div>Loading...</div>;
if (error) return <div>Error: {error}</div>;
return (
<div>
<button onClick={refetch}>Refresh</button>
<ul>
{data?.map(user => (
<li key={user.id}>{user.name}</li>
))}
</ul>
</div>
);
}
带认证的 Hook
import { useState, useEffect, useCallback } from 'react';
function useAuthFetch<T>(url: string, options?: RequestInit) {
const [data, setData] = useState<T | null>(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
const fetchData = useCallback(async () => {
setLoading(true);
setError(null);
try {
const token = localStorage.getItem('token');
const headers: HeadersInit = {
...options?.headers,
...(token ? { Authorization: `Bearer ${token}` } : {})
};
const res = await fetch(url, { ...options, headers });
if (res.status === 401) {
localStorage.removeItem('token');
window.location.href = '/login';
throw new Error('Unauthorized');
}
if (!res.ok) {
throw new Error(`Error: ${res.status}`);
}
const result = await res.json();
setData(result);
} catch (err: any) {
setError(err.message);
} finally {
setLoading(false);
}
}, [url, options]);
useEffect(() => {
fetchData();
}, [fetchData]);
return { data, loading, error, refetch: fetchData };
}
// 使用
function Dashboard() {
const { data, loading, error } = useAuthFetch('/api/dashboard');
if (loading) return <div>Loading...</div>;
if (error) return <div>Error: {error}</div>;
return <div>{JSON.stringify(data)}</div>;
}
表单提交 Hook
import { useState, useCallback } from 'react';
interface SubmitOptions<T> {
url: string;
method?: 'POST' | 'PUT' | 'PATCH';
onSuccess?: (data: T) => void;
onError?: (error: string) => void;
}
interface SubmitState {
loading: boolean;
error: string | null;
data: any | null;
}
function useSubmit<T>(options: SubmitOptions<T>) {
const [state, setState] = useState<SubmitState>({
loading: false,
error: null,
data: null
});
const submit = useCallback(async (body: any) => {
setState(prev => ({ ...prev, loading: true, error: null }));
try {
const token = localStorage.getItem('token');
const res = await fetch(options.url, {
method: options.method || 'POST',
headers: {
'Content-Type': 'application/json',
...(token ? { Authorization: `Bearer ${token}` } : {})
},
body: JSON.stringify(body)
});
if (!res.ok) {
throw new Error(`Error: ${res.status}`);
}
const data = await res.json();
setState({ loading: false, error: null, data });
options.onSuccess?.(data);
return data;
} catch (err: any) {
const errorMessage = err.message || 'Submission failed';
setState({ loading: false, error: errorMessage, data: null });
options.onError?.(errorMessage);
}
}, [options]);
return {
...state,
submit,
reset: () => setState({ loading: false, error: null, data: null })
};
}
// 使用
function LoginForm() {
const [form, setForm] = useState({ email: '', password: '' });
const { loading, error, submit } = useSubmit({
url: 'https://api.example.com/login',
method: 'POST',
onSuccess: (data) => {
localStorage.setItem('token', data.token);
window.location.href = '/dashboard';
}
});
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
await submit(form);
};
return (
<form onSubmit={handleSubmit}>
<input
type="email"
value={form.email}
onChange={e => setForm({ ...form, email: e.target.value })}
/>
<input
type="password"
value={form.password}
onChange={e => setForm({ ...form, password: e.target.value })}
/>
<button type="submit" disabled={loading}>
{loading ? 'Logging in...' : 'Login'}
</button>
{error && <p className="error">{error}</p>}
</form>
);
}
0x07 请求取消
AbortController
import { useState, useEffect, useRef } from 'react';
function SearchResults({ query }: { query: string }) {
const [results, setResults] = useState<any[]>([]);
const abortControllerRef = useRef<AbortController>();
useEffect(() => {
// 取消之前的请求
abortControllerRef.current?.abort();
if (!query) {
setResults([]);
return;
}
const controller = new AbortController();
abortControllerRef.current = controller;
fetch(`/api/search?q=${query}`, { signal: controller.signal })
.then(res => res.json())
.then(data => setResults(data))
.catch(err => {
if (err.name !== 'AbortError') {
console.error(err);
}
});
return () => controller.abort();
}, [query]);
return (
<ul>
{results.map(r => <li key={r.id}>{r.title}</li>)}
</ul>
);
}
React Query 取消
import { useQuery, useEffect } from '@tanstack/react-query';
function AutoSearch({ query }: { query: string }) {
const { data, isLoading } = useQuery({
queryKey: ['search', query],
queryFn: async ({ signal }) => {
const res = await fetch(`/api/search?q=${query}`, { signal });
return res.json();
},
enabled: query.length > 0, // 只有有内容时才搜索
staleTime: 0 // 立即标记为过时
});
return <div>{isLoading ? 'Searching...' : JSON.stringify(data)}</div>;
}
0x08 文件上传
import { useState } from 'react';
import { useMutation } from '@tanstack/react-query';
function FileUploader() {
const [file, setFile] = useState<File | null>(null);
const [progress, setProgress] = useState(0);
const uploadMutation = useMutation({
mutationFn: async (file: File) => {
const formData = new FormData();
formData.append('file', file);
const xhr = new XMLHttpRequest();
return new Promise((resolve, reject) => {
xhr.upload.addEventListener('progress', (e) => {
if (e.lengthComputable) {
setProgress(Math.round((e.loaded / e.total) * 100));
}
});
xhr.addEventListener('load', () => {
if (xhr.status >= 200 && xhr.status < 300) {
resolve(JSON.parse(xhr.responseText));
} else {
reject(new Error('Upload failed'));
}
});
xhr.addEventListener('error', reject);
xhr.open('POST', 'https://api.example.com/upload');
const token = localStorage.getItem('token');
if (token) {
xhr.setRequestHeader('Authorization', `Bearer ${token}`);
}
xhr.send(formData);
});
}
});
const handleFileChange = (e: React.ChangeEvent<HTMLInputElement>) => {
const selectedFile = e.target.files?.[0];
if (selectedFile) {
setFile(selectedFile);
uploadMutation.mutate(selectedFile);
}
};
return (
<div>
<input type="file" onChange={handleFileChange} />
{uploadMutation.isPending && (
<progress value={progress} max="100">{progress}%</progress>
)}
{uploadMutation.isSuccess && <p>Upload complete!</p>}
{uploadMutation.isError && <p>Upload failed</p>}
</div>
);
}
0x09 错误边界处理
import { Component, ReactNode } from 'react';
interface Props {
children: ReactNode;
fallback?: ReactNode;
}
interface State {
hasError: boolean;
error: Error | null;
}
class ErrorBoundary extends Component<Props, State> {
state: State = { hasError: false, error: null };
static getDerivedStateFromError(error: Error): State {
return { hasError: true, error };
}
componentDidCatch(error: Error, errorInfo: React.ErrorInfo) {
console.error('Error:', error, errorInfo);
// 可以在这里上报错误到服务器
// errorTracker.captureException(error);
}
render() {
if (this.state.hasError) {
return this.props.fallback || (
<div>
<h2>Something went wrong</h2>
<p>{this.state.error?.message}</p>
<button onClick={() => this.setState({ hasError: false, error: null })}>
Try again
</button>
</div>
);
}
return this.props.children;
}
}
// 使用
function App() {
return (
<ErrorBoundary>
<UserList />
</ErrorBoundary>
);
}
0x10 最佳实践
API 分层
// api/client.ts - axios 实例
import axios from 'axios';
const apiClient = axios.create({
baseURL: '/api',
timeout: 10000
});
apiClient.interceptors.response.use(
response => response,
error => {
if (error.response?.status === 401) {
// 处理未授权
}
return Promise.reject(error);
}
);
export default apiClient;
// api/endpoints.ts - API 端点
import apiClient from './client';
export const userApi = {
list: (params: any) => apiClient.get('/users', { params }),
get: (id: number) => apiClient.get(`/users/${id}`),
create: (data: any) => apiClient.post('/users', data),
update: (id: number, data: any) => apiClient.put(`/users/${id}`, data),
delete: (id: number) => apiClient.delete(`/users/${id}`)
};
export const productApi = {
list: (params: any) => apiClient.get('/products', { params }),
get: (id: number) => apiClient.get(`/products/${id}`)
};
// hooks/useUsers.ts - 业务 Hook
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
import { userApi } from '@/api/endpoints';
export function useUsers(params?: any) {
return useQuery({
queryKey: ['users', params],
queryFn: () => userApi.list(params)
});
}
export function useCreateUser() {
const queryClient = useQueryClient();
return useMutation({
mutationFn: userApi.create,
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['users'] });
}
});
}
// components/UserList.tsx - 组件
import { useUsers, useCreateUser } from '@/hooks/useUsers';
function UserList() {
const { data, isLoading } = useUsers({ page: 1 });
const createUser = useCreateUser();
// ...
}
环境变量配置
# .env
VITE_API_BASE_URL=https://api.example.com
VITE_API_TIMEOUT=10000
// config.ts
export const config = {
baseURL: import.meta.env.VITE_API_BASE_URL || '/api',
timeout: Number(import.meta.env.VITE_API_TIMEOUT) || 10000
};
参考
目录