不使用axios nextjs如何发送http请求 8 months ago
客户端
首先创建一个 API 工具函数:
这里以src/utils/api_client.tsx
为例:
interface FetchConfig extends RequestInit {
timeout?: number;
}
export async function fetchApi<T>(url: string, config: FetchConfig = {}): Promise<T> {
const { timeout = 8000, ...restConfig } = config;
try {
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), timeout);
const response = await fetch(url, {
...restConfig,
signal: controller.signal,
headers: {
'Content-Type': 'application/json',
...restConfig.headers,
},
});
clearTimeout(timeoutId);
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const data = await response.json();
return data as T;
} catch (error) {
if (error instanceof Error) {
throw new Error(`请求失败: ${error.message}`);
}
throw error;
}
}
然后创建一个示例组件来展示如何使用:
这里以src/components/examples/HttpExample.tsx
为例:
'use client'
import { useState, useEffect } from 'react'
import { fetchApi } from '@/utils/api_client'
interface Post {
id: number;
title: string;
body: string;
}
export default function HttpExample() {
const [posts, setPosts] = useState<Post[]>([])
const [loading, setLoading] = useState(false)
const [error, setError] = useState<string | null>(null)
// GET 请求示例
const fetchPosts = async () => {
try {
setLoading(true)
setError(null)
const data = await fetchApi<Post[]>('https://jsonplaceholder.typicode.com/posts')
setPosts(data.slice(0, 5)) // 只取前5条
} catch (err) {
setError(err instanceof Error ? err.message : '请求失败')
} finally {
setLoading(false)
}
}
// POST 请求示例
const createPost = async (title: string, body: string) => {
try {
const newPost = await fetchApi<Post>('https://jsonplaceholder.typicode.com/posts', {
method: 'POST',
body: JSON.stringify({
title,
body,
userId: 1,
}),
})
setPosts(prev => [newPost, ...prev])
} catch (err) {
setError(err instanceof Error ? err.message : '创建失败')
}
}
useEffect(() => {
fetchPosts()
}, [])
return (
<div className="p-4">
<h2 className="text-2xl font-bold mb-4">HTTP 请求示例</h2>
{/* 创建新帖子的表单 */}
<div className="mb-6">
<button
onClick={() => createPost('新文章', '这是文章内容')}
className="bg-blue-500 text-white px-4 py-2 rounded hover:bg-blue-600"
>
创建新文章
</button>
</div>
{/* 加载状态 */}
{loading && (
<div className="text-gray-500">加载中...</div>
)}
{/* 错误提示 */}
{error && (
<div className="text-red-500 mb-4">{error}</div>
)}
{/* 文章列表 */}
<div className="space-y-4">
{posts.map(post => (
<div key={post.id} className="border p-4 rounded">
<h3 className="font-bold">{post.title}</h3>
<p className="text-gray-600">{post.body}</p>
</div>
))}
</div>
</div>
)
}
展示:
import HttpExample from '@/components/examples/HttpExample'
export default function Home() {
return (
<main className="container mx-auto px-4 py-8">
<HttpExample />
</main>
)
}
其他常用请求方法示例:
// PUT 请求
const updatePost = async (id: number, data: Partial<Post>) => {
return fetchApi<Post>(`https://jsonplaceholder.typicode.com/posts/${id}`, {
method: 'PUT',
body: JSON.stringify(data),
})
}
// DELETE 请求
const deletePost = async (id: number) => {
return fetchApi(`https://jsonplaceholder.typicode.com/posts/${id}`, {
method: 'DELETE',
})
}
服务端
此示例为一个获取和展示用户列表的demo:
1, 首先创建类型定义文件
src/types/api.tsx
export interface User {
id: number;
name: string;
email: string;
company: {
name: string;
};
}
export interface ApiResponse<T> {
data: T;
status: number;
message: string;
}
src/utils/api_server.tsx
import { ApiResponse } from '@/types/api'
export async function fetchServer<T>(url: string, init?: RequestInit): Promise<ApiResponse<T>> {
try {
const response = await fetch(url, {
...init,
headers: {
'Content-Type': 'application/json',
...init?.headers,
},
next: { revalidate: 3600 }, // 1小时缓存
});
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const data = await response.json();
return {
data: data as T,
status: response.status,
message: 'success'
};
} catch (error) {
return {
data: [] as T,
status: 500,
message: error instanceof Error ? error.message : '请求失败'
};
}
}
src/components/examples/UserList.tsx
import { User } from '@/types/api'
import { fetchServer } from '@/utils/api_server'
async function getUsers() {
const { data } = await fetchServer<User[]>('https://jsonplaceholder.typicode.com/users');
return data;
}
export default async function UserList() {
const users = await getUsers();
return (
<div className="max-w-4xl mx-auto p-4">
<h2 className="text-2xl font-bold mb-6">用户列表</h2>
<div className="grid gap-4 md:grid-cols-2">
{users.map(user => (
<div
key={user.id}
className="p-4 border rounded-lg shadow-sm hover:shadow-md transition-shadow"
>
<h3 className="font-semibold text-lg">{user.name}</h3>
<p className="text-gray-600">{user.email}</p>
<p className="text-gray-500 text-sm mt-2">
公司: {user.company.name}
</p>
</div>
))}
</div>
</div>
);
}
loading.tsx
export default function Loading() {
return (
<div className="max-w-4xl mx-auto p-4">
<div className="h-8 w-48 bg-gray-200 rounded mb-6 animate-pulse" />
<div className="grid gap-4 md:grid-cols-2">
{[...Array(6)].map((_, index) => (
<div
key={index}
className="p-4 border rounded-lg"
>
<div className="h-6 w-3/4 bg-gray-200 rounded animate-pulse mb-2" />
<div className="h-4 w-1/2 bg-gray-200 rounded animate-pulse" />
</div>
))}
</div>
</div>
);
}
error.tsx
'use client'
interface ErrorProps {
error: Error & { digest?: string };
reset: () => void;
}
export default function ErrorComponent({ reset }: ErrorProps) {
return (
<div className="max-w-4xl mx-auto p-4">
<div className="text-center">
<h2 className="text-2xl font-bold text-red-500 mb-4">
加载失败
</h2>
<button
onClick={reset}
className="px-4 py-2 bg-blue-500 text-white rounded hover:bg-blue-600 transition"
>
重试
</button>
</div>
</div>
);
}