基于React的API数据获取与展示:axios实战指南
构建高效数据获取应用:React与axios结合实践
在现代Web应用开发中,从API获取并展示数据是一项常见任务。本文将详细介绍如何使用React和axios库构建一个功能完善的数据展示应用,涵盖加载状态管理、错误处理、数据渲染和搜索功能等关键环节。
项目概述
我们将创建一个文章展示应用,该应用从jsonplaceholder提供的测试API获取文章数据,并以卡片形式展示。应用将包含以下核心功能:
- 异步获取文章数据
- 加载状态指示器
- 错误处理机制
- 响应式卡片布局
- 标题搜索过滤功能
技术栈准备
本项目主要使用以下技术:
- React 18(函数组件与Hooks)
- axios(HTTP客户端)
- CSS3(响应式样式)
- ES6+(现代JavaScript特性)
项目实现步骤
1. 项目初始化
首先,我们需要创建一个React应用。可以使用Create React App工具快速初始化项目:
npx create-react-api-app article-display-app
cd article-display-app
2. 安装依赖
项目中需要安装axios库来处理HTTP请求:
npm install axios
3. 组件结构设计
我们将创建以下组件:
- App(主组件)
- ArticleCard(文章卡片组件)
- LoadingIndicator(加载指示器)
- ErrorMessage(错误信息组件)
- SearchBar(搜索框组件)
4. 实现数据获取逻辑
在App组件中,我们将使用useState和useEffect来管理数据获取:
import React, { useState, useEffect } from 'react';
import axios from 'axios';
const ArticleApp = () => {
// 状态管理
const [articles, setArticles] = useState([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
const [searchTerm, setSearchTerm] = useState('');
// 获取文章数据
useEffect(() => {
const fetchArticles = async () => {
try {
setLoading(true);
const response = await axios.get('https://jsonplaceholder.typicode.com/posts');
setArticles(response.data);
setError(null);
} catch (err) {
setError('获取数据失败,请稍后重试');
console.error('获取数据错误:', err);
} finally {
setLoading(false);
}
};
fetchArticles();
}, []);
// 处理搜索
const handleSearch = (event) => {
setSearchTerm(event.target.value);
};
// 过滤文章
const filteredArticles = articles.filter(article =>
article.title.toLowerCase().includes(searchTerm.toLowerCase())
);
return (
<div className="app-container">
<h1>文章展示应用</h1>
{/* 搜索框 */}
<div className="search-container">
<input
type="text"
placeholder="搜索文章标题..."
value={searchTerm}
onChange={handleSearch}
className="search-input"
/>
</div>
{/* 根据状态渲染不同内容 */}
{loading ? (
<LoadingIndicator />
) : error ? (
<ErrorMessage message={error} />
) : (
<div className="articles-grid">
{filteredArticles.map(article => (
<ArticleCard key={article.id} article={article} />
))}
</div>
)}
</div>
);
};
5. 实现子组件
以下是各个子组件的实现:
// 文章卡片组件
const ArticleCard = ({ article }) => {
return (
<div className="article-card">
<h2 className="article-title">{article.title}</h2>
<p className="article-body">{article.body.substring(0, 150)}...</p>
<div className="article-meta">
<span>ID: {article.id}</span>
<span>用户ID: {article.userId}</span>
</div>
</div>
);
};
// 加载指示器组件
const LoadingIndicator = () => {
return (
<div className="loading-container">
<div className="spinner"></div>
<p>正在加载数据,请稍候...</p>
</div>
);
};
// 错误信息组件
const ErrorMessage = ({ message }) => {
return (
<div className="error-container">
<div className="error-icon">⚠️</div>
<p>{message}</p>
</div>
);
};
6. 样式设计
以下是应用的CSS样式,采用响应式设计:
/* 全局样式 */
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
line-height: 1.6;
color: #333;
background-color: #f5f5f5;
}
.app-container {
max-width: 1200px;
margin: 0 auto;
padding: 20px;
}
h1 {
text-align: center;
margin-bottom: 30px;
color: #2c3e50;
}
/* 搜索框样式 */
.search-container {
margin-bottom: 30px;
display: flex;
justify-content: center;
}
.search-input {
width: 100%;
max-width: 500px;
padding: 12px 15px;
border: 1px solid #ddd;
border-radius: 25px;
font-size: 16px;
transition: border-color 0.3s;
}
.search-input:focus {
outline: none;
border-color: #3498db;
}
/* 文章卡片网格 */
.articles-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
gap: 20px;
}
/* 文章卡片样式 */
.article-card {
background: white;
border-radius: 8px;
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
padding: 20px;
transition: transform 0.3s, box-shadow 0.3s;
}
.article-card:hover {
transform: translateY(-5px);
box-shadow: 0 5px 15px rgba(0, 0, 0, 0.2);
}
.article-title {
font-size: 1.25rem;
margin-bottom: 10px;
color: #2980b9;
}
.article-body {
color: #555;
margin-bottom: 15px;
}
.article-meta {
display: flex;
justify-content: space-between;
font-size: 0.85rem;
color: #777;
}
/* 加载指示器 */
.loading-container {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
min-height: 300px;
}
.spinner {
width: 50px;
height: 50px;
border: 5px solid #f3f3f3;
border-top: 5px solid #3498db;
border-radius: 50%;
animation: spin 1s linear infinite;
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
/* 错误信息 */
.error-container {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
min-height: 300px;
background-color: #fff;
border-radius: 8px;
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
padding: 30px;
text-align: center;
}
.error-icon {
font-size: 2rem;
margin-bottom: 15px;
}
/* 响应式调整 */
@media (max-width: 768px) {
.articles-grid {
grid-template-columns: 1fr;
}
.search-input {
max-width: 100%;
}
}
优化与扩展
当前应用已经实现了基本功能,但还可以进一步优化和扩展:
1. 性能优化
对于大量数据,可以考虑使用React.memo和useMemo来优化性能:
const ArticleCard = React.memo(({ article }) => {
// 组件实现...
});
const filteredArticles = useMemo(() => {
return articles.filter(article =>
article.title.toLowerCase().includes(searchTerm.toLowerCase())
);
}, [articles, searchTerm]);
2. 添加分页功能
当数据量较大时,实现分页是必要的:
const [currentPage, setCurrentPage] = useState(1);
const articlesPerPage = 10;
// 计算分页
const indexOfLastArticle = currentPage * articlesPerPage;
const indexOfFirstArticle = indexOfLastArticle - articlesPerPage;
const currentArticles = filteredArticles.slice(indexOfFirstArticle, indexOfLastArticle);
// 分页控件
const renderPagination = () => {
const pageNumbers = [];
for (let i = 1; i <= Math.ceil(filteredArticles.length / articlesPerPage); i++) {
pageNumbers.push(i);
}
return (
<div className="pagination">
{pageNumbers.map(number => (
<button
key={number}
onClick={() => setCurrentPage(number)}
className={currentPage === number ? 'active' : ''}
>
{number}
</button>
))}
</div>
);
};
3. 错误重试机制
添加重试按钮,提升用户体验:
const ErrorMessage = ({ message, onRetry }) => {
return (
<div className="error-container">
<div className="error-icon">⚠️</div>
<p>{message}</p>
<button onClick={onRetry} className="retry-button">
重试
</button>
</div>
);
};