React 商品搜索与 API 演示项目全解析
这是一篇基于 React + Vite + React Router 构建的完整前端项目博客。
项目实现了:商品搜索(前端静态数据)、多页面路由、深色主题 UI、以及通过 GET/POST 请求调用公开 API 的功能。
本文将逐文件分析代码逻辑、样式设计以及关键 React 概念(Hooks、路由、状态管理、网络请求)。
📁 项目结构
text
text
src/
├── assets/ # 静态资源(未使用)
├── data/
│ └── products.js # 商品模拟数据
├── pages/
│ ├── Home.jsx # 首页组件
│ ├── Home.css
│ ├── Search.jsx # 商品搜索页组件
│ ├── Search.css
│ ├── ApiPage.jsx # API 请求演示页组件
│ └── ApiPage.css
├── App.jsx # 根组件(路由配置 + 导航栏)
├── App.css # 全局导航栏样式
├── index.css # 全局重置 + 深色主题
├── main.jsx # 入口文件(挂载路由)
└── ...
🔧 一、项目入口与全局配置
1. main.jsx -- React 根渲染与路由包裹
jsx
// src/main.jsx
import React from "react";
import ReactDOM from "react-dom/client";
import { BrowserRouter } from "react-router-dom";
import App from "./App";
import "./index.css";
ReactDOM.createRoot(document.getElementById("root")).render(
<React.StrictMode>
<BrowserRouter>
<App />
</BrowserRouter>
</React.StrictMode>
);
解释:
- 使用
ReactDOM.createRoot开启 React 18 的并发渲染。 <BrowserRouter>提供基于 HTML5 History API 的路由上下文,使<App />内部可以使用useRoutes、Link等路由钩子。- 全局引入
index.css作为基础样式。
2. index.css -- 全局深色主题与重置
css
/* src/index.css */
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
html, body, #root {
width: 100%;
min-height: 100%;
background: #0a0f1c; /* 深蓝黑背景 */
}
body {
font-family: 'Segoe UI', 'Inter', system-ui, sans-serif;
color: #eef2ff;
line-height: 1.5;
}
::-webkit-scrollbar {
width: 8px;
}
::-webkit-scrollbar-track {
background: #1e293b;
}
::-webkit-scrollbar-thumb {
background: #3b82f6;
border-radius: 4px;
}
a {
text-decoration: none;
color: inherit;
}
解释:
- 全局盒模型重置,避免边距混乱。
- 设置全屏深色背景(
#0a0f1c),与后续组件风格统一。 - 自定义滚动条样式,增加蓝绿色点缀。
- 移除链接默认下划线,保持视觉干净。
🧭 二、根组件与路由导航
3. App.jsx -- 导航栏与路由配置
jsx
// src/App.jsx
import { Routes, Route, Link } from "react-router-dom";
import Home from "./pages/Home";
import Search from "./pages/Search";
import ApiPage from "./pages/ApiPage";
import "./App.css";
function App() {
return (
<div>
<nav className="navbar">
<Link to="/">🏠 首页</Link>
<Link to="/search">🔍 商品搜索</Link>
<Link to="/api">✨ 开放数据</Link>
</nav>
<Routes>
<Route path="/" element={<Home />} />
<Route path="/search" element={<Search />} />
<Route path="/api" element={<ApiPage />} />
</Routes>
</div>
);
}
export default App;
解释:
- 使用
react-router-dom的Link组件实现无刷新跳转。 - 导航栏使用
className="navbar",样式在App.css中定义。 <Routes>包裹多个<Route>,匹配当前路径并渲染对应页面组件。- 三个页面:首页(Home)、商品搜索(Search)、API 演示(ApiPage)。
4. App.css -- 导航栏毛玻璃效果
css
/* src/App.css */
.navbar {
background: rgba(15, 23, 42, 0.9);
backdrop-filter: blur(12px);
padding: 1rem 2rem;
display: flex;
gap: 2rem;
border-bottom: 1px solid rgba(59, 130, 246, 0.4);
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.3);
position: sticky;
top: 0;
z-index: 10;
}
.navbar a {
font-weight: 600;
font-size: 1.1rem;
padding: 0.5rem 1rem;
border-radius: 40px;
transition: all 0.2s ease;
background: rgba(255,255,255,0.05);
}
.navbar a:hover {
background: linear-gradient(135deg, #3b82f6, #10b981);
color: white;
transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(59,130,246,0.3);
}
解释:
backdrop-filter: blur(12px)实现毛玻璃效果,半透明背景。sticky定位使导航栏固定在顶部。- 每个导航链接带圆角、半透背景,hover 时展示蓝绿渐变并轻微上浮。
- 整体风格与深色主题完美融合。
🏠 三、首页(Home)组件 -- 产品介绍与特性卡片
5. Home.jsx -- 首页结构
jsx
// src/pages/Home.jsx
import { Link } from "react-router-dom";
import "./Home.css";
function Home() {
return (
<div className="home-page">
<div className="hero">
<h1 className="glow-text">✨ React 商品探索</h1>
<p className="subtitle">基于 React Router + Hooks 的静态商品搜索演示</p>
<Link to="/search">
<button className="cta-button">🔍 立即搜索商品 →</button>
</Link>
</div>
<div className="features">
<div className="feature-card">
<div className="feature-icon">⚛️</div>
<h3>React 函数组件</h3>
<p>使用 useState 管理状态,useEffect 概念预留扩展</p>
</div>
<div className="feature-card">
<div className="feature-icon">🧭</div>
<h3>React Router v6</h3>
<p>声明式路由,支持多页面导航与动态路由</p>
</div>
<div className="feature-card">
<div className="feature-icon">🔎</div>
<h3>前端模糊搜索</h3>
<p>本地商品数据,实时筛选不区分大小写</p>
</div>
</div>
<footer className="home-footer">
<p>💡 提示:点击上方导航或按钮进入搜索页,试试搜索"手机"、"苹果"</p>
</footer>
</div>
);
}
export default Home;
解释:
- 使用
flex布局实现垂直居中(配合.home-page样式)。 - 标题使用渐变文字(CSS 中定义),带有微弱光晕。
- 三个特性卡片(feature-card)采用毛玻璃效果、hover 上浮动画。
- 底部 footer 提供简短的使用提示。
- 通过
Link跳转到搜索页。
6. Home.css -- 渐变标题、卡片动画
css
/* src/pages/Home.css */
.home-page {
min-height: calc(100vh - 70px);
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 2rem;
}
.hero h1 {
font-size: 3.5rem;
background: linear-gradient(135deg, #60a5fa, #34d399);
background-clip: text;
-webkit-background-clip: text;
color: transparent;
}
.cta-button {
background: linear-gradient(95deg, #3b82f6, #10b981);
border: none;
padding: 12px 32px;
border-radius: 40px;
color: white;
box-shadow: 0 8px 20px rgba(59,130,246,0.4);
transition: transform 0.2s, box-shadow 0.2s;
}
.cta-button:hover {
transform: scale(1.03);
box-shadow: 0 12px 28px rgba(59,130,246,0.6);
}
.feature-card {
background: rgba(30, 41, 59, 0.6);
backdrop-filter: blur(8px);
border-radius: 28px;
padding: 1.8rem;
transition: all 0.3s;
}
.feature-card:hover {
transform: translateY(-6px);
border-color: #3b82f6;
}
解释:
- 标题文字采用
background-clip: text实现蓝绿渐变。 - 按钮采用相同渐变色,hover 放大并增强阴影,营造"发光"感。
- 卡片 hover 时向上平移,并增加蓝色边框,交互感强。
🔍 四、商品搜索页(Search) -- 静态数据 + 模糊搜索
7. 模拟商品数据 products.js
js
// src/data/products.js
export const products = [
{ id: 1, name: "苹果 iPhone 15", category: "手机", price: 5999 },
{ id: 2, name: "华为 Mate 60", category: "手机", price: 5499 },
{ id: 3, name: "小米 14 Pro", category: "手机", price: 4999 },
{ id: 4, name: "联想 ThinkPad X1", category: "笔记本", price: 8999 },
{ id: 5, name: "戴尔 XPS 13", category: "笔记本", price: 9999 },
{ id: 6, name: "索尼 WH-1000XM5", category: "耳机", price: 1999 },
{ id: 7, name: "苹果 AirPods Pro 2", category: "耳机", price: 1899 }
];
解释:
- 前端静态数据,模拟商品列表。包含
id、name、category、price。 - 搜索时基于
name字段进行模糊匹配。
8. Search.jsx -- 核心搜索逻辑
jsx
// src/pages/Search.jsx
import { useState } from "react";
import { products } from "../data/products";
import "./Search.css";
function Search() {
const [keyword, setKeyword] = useState("");
const [searchResults, setSearchResults] = useState([]);
const [hasSearched, setHasSearched] = useState(false);
const handleSearch = () => {
if (!keyword.trim()) {
setSearchResults([]);
setHasSearched(true);
return;
}
const filtered = products.filter(product =>
product.name.toLowerCase().includes(keyword.toLowerCase())
);
setSearchResults(filtered);
setHasSearched(true);
};
const quickSearch = (term) => {
setKeyword(term);
const filtered = products.filter(product =>
product.name.toLowerCase().includes(term.toLowerCase())
);
setSearchResults(filtered);
setHasSearched(true);
};
return (
<div className="search-page">
<div className="search-container">
<div className="search-header">
<h2>✨ 商品搜索</h2>
</div>
<div className="search-box">
<input
type="text"
placeholder="输入商品名称,例如:iPhone"
value={keyword}
onChange={(e) => setKeyword(e.target.value)}
onKeyPress={(e) => e.key === "Enter" && handleSearch()}
/>
<button onClick={handleSearch}>搜索</button>
</div>
{hasSearched && (
<div className="results">
<div className="result-count">共找到 {searchResults.length} 件商品</div>
{searchResults.length === 0 ? (
<div className="empty-result">😞 没有找到相关商品</div>
) : (
<ul className="product-list">
{searchResults.map(product => (
<li key={product.id} className="product-item">
<span className="product-name">{product.name}</span>
<span className="product-category">{product.category}</span>
<span className="product-price">¥{product.price}</span>
</li>
))}
</ul>
)}
</div>
)}
<div className="demo-tips">
<span>⚡ 试试快速搜索:</span>
<button onClick={() => quickSearch("手机")}>手机</button>
<button onClick={() => quickSearch("苹果")}>苹果</button>
<button onClick={() => quickSearch("笔记本")}>笔记本</button>
</div>
</div>
</div>
);
}
export default Search;
解释:
- 使用
useState管理关键词、搜索结果、是否已搜索过。 handleSearch执行模糊匹配(转小写后includes),更新结果。- 按回车键(
onKeyPress)同样触发搜索。 quickSearch函数在设置关键词的同时立即执行搜索,避免异步状态延迟。- 搜索结果渲染为自定义列表项,每个商品展示名称、分类、价格。
- 快速搜索按钮方便演示。
9. Search.css -- 深色卡片与搜索结果样式
css
.search-box {
display: flex;
gap: 12px;
background: rgba(30, 41, 59, 0.5);
backdrop-filter: blur(8px);
border-radius: 60px;
border: 1px solid rgba(59,130,246,0.4);
}
.search-box input {
background: transparent;
color: #f1f5f9;
}
.product-item {
background: rgba(30, 41, 59, 0.7);
border-left: 4px solid #3b82f6;
transition: 0.2s;
}
.product-item:hover {
transform: translateX(6px);
border-left-color: #10b981;
}
.product-price {
color: #34d399;
font-weight: bold;
}
解释:
- 搜索框毛玻璃风格,输入框透明,按钮渐变色。
- 每个商品卡片带左侧蓝色条,hover 时右移并变为绿色。
- 价格使用绿色高亮(
#34d399),分类使用圆角标签。 - 整体保持深色半透明背景,与全局主题一致。
🌐 五、API 演示页(ApiPage) -- GET / POST 请求
10. ApiPage.jsx -- 动态请求多种公开 API
jsx
// src/pages/ApiPage.jsx
import { useState, useEffect } from "react";
import "./ApiPage.css";
const apiList = [
{ id: 'posts', name: 'GET 示例数据 (JSONPlaceholder)', url: 'https://jsonplaceholder.typicode.com/posts', method: 'GET' },
{ id: 'countries', name: 'GET 国家信息 (REST Countries)', url: 'https://restcountries.com/v3.1/all', method: 'GET' },
{ id: 'spacex', name: 'GET SpaceX 最新发射', url: 'https://api.spacexdata.com/v5/launches/latest', method: 'GET' },
{
id: 'post',
name: 'POST 创建新数据 (JSONPlaceholder)',
url: 'https://jsonplaceholder.typicode.com/posts',
method: 'POST',
bodyData: { title: 'React Demo 商品', body: '这是一条通过 POST 请求创建的商品记录', userId: 1 }
}
];
function ApiPage() {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(false);
const [error, setError] = useState(null);
const [selectedApi, setSelectedApi] = useState(apiList[0]);
const fetchData = async () => {
setLoading(true);
setError(null);
try {
const { url, method, bodyData } = selectedApi;
let response;
if (method === 'GET') {
response = await fetch(url);
} else if (method === 'POST') {
response = await fetch(url, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(bodyData),
});
}
if (!response.ok) throw new Error(`HTTP ${response.status}`);
const result = await response.json();
const formattedData = method === 'GET' && Array.isArray(result) ? result.slice(0, 5) : result;
setData({ method, url, requestBody: method === 'POST' ? bodyData : null, response: formattedData });
} catch (err) {
setError(err.message);
} finally {
setLoading(false);
}
};
useEffect(() => { fetchData(); }, [selectedApi]);
return (
<div className="api-page">
<div className="api-container">
<div className="api-header">
<h2>📡 探索开放数据 (GET + POST)</h2>
<p>从免费的公共 API 获取真实数据,并尝试向服务器发送新数据</p>
</div>
<div className="api-selector">
<label>选择数据源:</label>
<select value={selectedApi.id} onChange={(e) => setSelectedApi(apiList.find(api => api.id === e.target.value))}>
{apiList.map(api => (<option key={api.id} value={api.id}>{api.method} -- {api.name}</option>))}
</select>
<button onClick={fetchData} disabled={loading}>{loading ? '加载中...' : '重新获取'}</button>
</div>
<div className="api-result">
{loading && <div className="loading-state"><div className="spinner"></div><p>正在执行 {selectedApi.method} 请求...</p></div>}
{error && <div className="error-state"><p>❌ {error}</p><button onClick={fetchData}>重试</button></div>}
{data && (
<div className="data-display">
<div className="data-meta">
<span>请求方法: {data.method}</span>
<span>请求地址: {data.url}</span>
</div>
{data.method === 'POST' && data.requestBody && (
<div className="request-preview">
<div className="section-title">📤 发送的请求体 (JSON):</div>
<pre className="json-viewer request-body">{JSON.stringify(data.requestBody, null, 2)}</pre>
</div>
)}
<div className="section-title">📥 服务器响应:</div>
<pre className="json-viewer">{JSON.stringify(data.response, null, 2)}</pre>
</div>
)}
</div>
</div>
</div>
);
}
export default ApiPage;
解释:
- 定义
apiList包含 GET(示例数据、国家信息、SpaceX 发射)和 POST(创建新数据)接口。 - 使用
useState管理数据、加载状态、错误以及当前选中的 API。 fetchData根据 method 分别构造fetch请求,POST 时携带 JSON 体。- 对 GET 返回的数组只显示前 5 条,避免页面过长。
useEffect监听selectedApi变化,自动请求新数据。- 界面包含下拉选择器、重新获取按钮,以及美观的 JSON 查看器(深色代码块)。
- 错误处理:若请求失败,显示错误信息并提供重试按钮。
11. ApiPage.css -- 响应式深色代码块
css
.api-page {
min-height: 100vh;
background: #0a0f1c;
padding: 2rem;
}
.api-container {
max-width: 1200px;
margin: 0 auto;
background: rgba(15, 23, 42, 0.85);
backdrop-filter: blur(10px);
border-radius: 28px;
padding: 2rem;
border: 1px solid rgba(59,130,246,0.3);
}
.json-viewer {
background: #0f172a;
padding: 1rem;
border-radius: 16px;
font-family: monospace;
color: #e2e8f0;
white-space: pre-wrap;
word-break: break-word;
}
.spinner {
width: 40px;
height: 40px;
border: 3px solid rgba(59,130,246,0.3);
border-top-color: #3b82f6;
border-radius: 50%;
animation: spin 0.8s linear infinite;
}
解释:
- 背景与全局一致,容器采用毛玻璃 + 大圆角。
- JSON 数据使用深色代码块,等宽字体,自动换行。
- 加载动画(spinner)用纯 CSS 实现旋转效果。
- 响应式布局:在小屏幕上调整 padding 和选择器排列方向。
🧪 六、关键 React 概念总结
| 概念 | 体现位置 | 说明 |
|---|---|---|
| 函数组件 | 所有 .jsx 文件 |
React 推荐写法,配合 Hooks 实现状态与副作用 |
| useState | Search, ApiPage | 管理输入框值、搜索结果、加载状态等 |
| useEffect | ApiPage | 监听 selectedApi 变化,自动发起网络请求 |
| 事件处理 | Search, ApiPage | onClick, onChange, onKeyPress |
| 条件渲染 | Search, ApiPage | 三元运算符 / && 控制加载、错误、结果区域的显示 |
| 列表渲染 & key | Search | products.map() 生成商品列表,key 使用唯一 id |
| 路由 (React Router) | App.jsx, main.jsx | BrowserRouter, Routes, Route, Link |
| Props 传递 | 未显式使用(但可以通过扩展轻松加入) | 组件通信基础 |
| 网络请求 (fetch) | ApiPage | GET / POST 异步请求,处理响应与错误 |
🎨 七、样式设计亮点
- 深色主题 :全局背景
#0a0f1c,文字#eef2ff,对比舒适。 - 毛玻璃效果 :
backdrop-filter: blur()应用于导航栏、卡片、API 容器。 - 蓝绿渐变点缀 :标题、按钮、hover 状态使用
linear-gradient(135deg, #60a5fa, #34d399)。 - 微交互动画:按钮缩放、卡片平移、边框颜色切换,提升用户体验。
- 响应式设计 :移动端适配(
@media)调整内边距和布局方向。
🚀 八、运行与扩展建议
运行项目
bash
npm install
npm run dev
访问:
http://localhost:5173/→ 首页/search→ 商品搜索/api→ API 演示(默认 GET JSONPlaceholder)
扩展方向
- 添加更多 API :在
apiList中增加其他公开接口(如天气、加密货币)。 - 实现用户自定义 POST 数据 :添加表单输入框,动态构造
bodyData。 - 加入状态管理:当项目变大时,引入 Redux 或 Zustand 管理全局用户偏好。
- 商品数据接入后端:将商品搜索改为调用真实接口,并增加分页功能。
- 表单处理库 :使用
react-hook-form优化搜索输入。
📝 结语
本文详细解析了一个完整的 React 演示项目,涵盖:
- 路由与导航
- 组件化开发与 Hooks 使用
- 前端静态搜索
- 调用第三方 API(GET / POST)
- 现代化深色 UI 与交互动效
所有代码均已提供并附有行内注释与解释,你可以直接复制使用,也可以作为学习 React 的实践范例。如果有任何疑问或改进建议,欢迎在评论区留言!
Happy coding! ⚛️