文章目录
- 一、自然语言描述转化的基础组件代码(React + TypeScript)
- 二、组件二次优化(性能 + 体验 + 扩展性升级)
- 优化 1:性能优化(避免重复渲染 + 数据缓存)
- 优化 2:交互体验升级
- 优化 3:扩展性升级(支持自定义配置)
- 优化后组件核心优势
一、自然语言描述转化的基础组件代码(React + TypeScript)
import React, { useState, useEffect } from 'react'; import { Input, Select, Checkbox, Pagination, Card, Spin, Empty } from 'antd'; import { SearchOutlined, FilterOutlined } from '@ant-design/icons'; // 商品类型定义 interface Product { id: number; name: string; price: number; category: string; brand: string; sales: number; image: string; } // 筛选条件类型 interface FilterParams { keyword: string; category: string; brand: string[]; priceRange: [number, number]; } const ProductList: React.FC = () => { // 状态管理 const [products, setProducts] = useState<Product[]>([]); const [filteredProducts, setFilteredProducts] = useState<Product[]>([]); const [filters, setFilters] = useState<FilterParams>({ keyword: '', category: 'all', brand: [], priceRange: [0, 10000], }); const [loading, setLoading] = useState<boolean>(true); const [currentPage, setCurrentPage] = useState<number>(1); const [pageSize] = useState<number>(12); // 模拟接口请求商品数据 useEffect(() => { const fetchProducts = async () => { setLoading(true); // 模拟延迟 await new Promise(resolve => setTimeout(resolve, 800)); // 模拟商品数据 const mockData: Product[] = Array.from({ length: 60 }, (_, i) => ({ id: i + 1, name: `智能防蓝光眼镜 ${i + 1}代 - ${['商务款', '休闲款', '运动款'][i % 3]}`, price: 199 + Math.floor(Math.random() * 800), category: ['防蓝光', '近视镜', '太阳镜', '老花镜'][i % 4], brand: ['暴龙', '陌森', '雷朋', '帕森', '海伦凯勒'][i % 5], sales: Math.floor(Math.random() * 1000), image: `https://picsum.photos/200/200?random=${i}`, })); setProducts(mockData); setFilteredProducts(mockData); setLoading(false); }; fetchProducts(); }, []); // 筛选逻辑 useEffect(() => { let result = [...products]; // 关键词筛选 if (filters.keyword) { result = result.filter(product => product.name.toLowerCase().includes(filters.keyword.toLowerCase()) ); } // 分类筛选 if (filters.category !== 'all') { result = result.filter(product => product.category === filters.category); } // 品牌筛选 if (filters.brand.length > 0) { result = result.filter(product => filters.brand.includes(product.brand)); } // 价格范围筛选 result = result.filter( product => product.price >= filters.priceRange[0] && product.price <= filters.priceRange[1] ); setFilteredProducts(result); setCurrentPage(1); // 筛选后重置到第一页 }, [filters, products]); // 处理筛选条件变更 const handleFilterChange = (key: keyof FilterParams, value: any) => { setFilters(prev => ({ ...prev, [key]: value })); }; // 分页数据计算 const paginatedProducts = filteredProducts.slice( (currentPage - 1) * pageSize, currentPage * pageSize ); // 分类选项(从商品数据中提取唯一值) const categoryOptions = Array.from(new Set(products.map(p => p.category))).map(cat => ({ label: cat, value: cat, })); // 品牌选项(从商品数据中提取唯一值) const brandOptions = Array.from(new Set(products.map(p => p.brand))).map(brand => ({ label: brand, value: brand, })); return ( <div style={{ padding: '20px', maxWidth: '1400px', margin: '0 auto' }}> <h2 style={{ marginBottom: '20px', fontWeight: 600 }}>眼镜商品列表</h2> {/* 筛选区域 */} <div style={{ background: '#f8f9fa', padding: '20px', borderRadius: '8px', marginBottom: '20px', display: 'flex', flexWrap: 'wrap', gap: '16px', alignItems: 'center' }}> <div style={{ flex: 1, minWidth: '200px' }}> <Input placeholder="搜索商品名称..." prefix={<SearchOutlined />} value={filters.keyword} onChange={e => handleFilterChange('keyword', e.target.value)} style={{ width: '100%' }} /> </div> <div style={{ width: '180px' }}> <Select placeholder="选择分类" value={filters.category} onChange={value => handleFilterChange('category', value)} style={{ width: '100%' }} > <Select.Option value="all">全部分类</Select.Option> {categoryOptions} </Select> </div> <div style={{ flex: 1, minWidth: '250px' }}> <div style={{ marginBottom: '8px', fontSize: '14px', color: '#666' }}>选择品牌</div> <div style={{ display: 'flex', flexWrap: 'wrap', gap: '8px' }}> {brandOptions.map(brand => ( <Checkbox key={brand.value} checked={filters.brand.includes(brand.value)} onChange={e => { const newBrands = e.target.checked ? [...filters.brand, brand.value] : filters.brand.filter(b => b !== brand.value); handleFilterChange('brand', newBrands); }} > {brand.label} </Checkbox> ))} </div> </div> <div style={{ width: '220px' }}> <div style={{ marginBottom: '8px', fontSize: '14px', color: '#666' }}>价格范围</div> <div style={{ display: 'flex', gap: '8px' }}> <Input type="number" placeholder="最低" value={filters.priceRange[0] || ''} onChange={e => handleFilterChange('priceRange', [ Number(e.target.value) || 0, filters.priceRange[1] ])} style={{ width: '50%' }} /> <Input type="number" placeholder="最高" value={filters.priceRange[1] || ''} onChange={e => handleFilterChange('priceRange', [ filters.priceRange[0], Number(e.target.value) || 10000 ])} style={{ width: '50%' }} /> </div> </div> <div> <button style={{ background: '#1890ff', color: 'white', border: 'none', borderRadius: '4px', padding: '8px 16px', cursor: 'pointer', display: 'flex', alignItems: 'center', gap: '4px' }} onClick={() => setFilters({ keyword: '', category: 'all', brand: [], priceRange: [0, 10000], })} > <FilterOutlined /> 重置筛选 </button> </div> </div> {/* 商品列表区域 */} <Spin spinning={loading} tip="加载商品中..."> {filteredProducts.length === 0 ? ( <Empty description="暂无符合条件的商品" style={{ padding: '50px 0' }} /> ) : ( <> <div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fill, minmax(220px, 1fr))', gap: '20px', marginBottom: '20px' }}> {paginatedProducts.map(product => ( <Card key={product.id} hoverable cover={<img src={product.image} alt={product.name} style={{ height: '200px', objectFit: 'cover' }} />} style={{ borderRadius: '8px', border: '1px solid #e8e8e8' }} > <Card.Meta title={ <div style={{ fontSize: '14px', fontWeight: 500, marginBottom: '8px', display: '-webkit-box', WebkitLineClamp: 2, WebkitBoxOrient: 'vertical', overflow: 'hidden' }}> {product.name} </div> } description={ <div> <span style={{ color: '#ff4d4f', fontSize: '18px', fontWeight: 600 }}> ¥{product.price} </span> <div style={{ fontSize: '12px', color: '#999', marginTop: '4px', display: 'flex', justifyContent: 'space-between' }}> <span>品牌:{product.brand}</span> <span>销量:{product.sales}+</span> </div> </div> } /> </Card> ))} </div> {/* 分页区域 */} <div style={{ display: 'flex', justifyContent: 'center', marginTop: '30px' }}> <Pagination current={currentPage} pageSize={pageSize} total={filteredProducts.length} onChange={setCurrentPage} showSizeChanger={false} showQuickJumper showTotal={(total) => `共 ${total} 件商品`} /> </div> </> )} </Spin> </div> ); }; export default ProductList;
二、组件二次优化(性能 + 体验 + 扩展性升级)
优化 1:性能优化(避免重复渲染 + 数据缓存)
// 1. 引入React.memo和useMemo/useCallback import React, { useState, useEffect, useMemo, useCallback } from 'react'; // 2. 商品卡片组件单独抽离并使用memo缓存 const ProductCard = React.memo(({ product }: { product: Product }) => ( <Card hoverable cover={<img src={product.image} alt={product.name} style={{ height: '200px', objectFit: 'cover' }} />} style={{ borderRadius: '8px', border: '1px solid #e8e8e8', transition: 'transform 0.3s ease' }} onMouseEnter={(e) => e.currentTarget.style.transform = 'translateY(-5px)'} onMouseLeave={(e) => e.currentTarget.style.transform = 'translateY(0)'} > <Card.Meta title={ <div style={{ fontSize: '14px', fontWeight: 500, marginBottom: '8px', display: '-webkit-box', WebkitLineClamp: 2, WebkitBoxOrient: 'vertical', overflow: 'hidden' }}> {product.name} </div> } description={ <div> <span style={{ color: '#ff4d4f', fontSize: '18px', fontWeight: 600 }}> ¥{product.price} </span> <div style={{ fontSize: '12px', color: '#999', marginTop: '4px', display: 'flex', justifyContent: 'space-between' }}> <span>品牌:{product.brand}</span> <span>销量:{product.sales}+</span> </div> </div> } /> </Card> )); // 3. 筛选逻辑用useMemo缓存,依赖变化才重新计算 const filteredProducts = useMemo(() => { return products.filter(product => { const matchKeyword = product.name.toLowerCase().includes(filters.keyword.toLowerCase()); const matchCategory = filters.category === 'all' ? true : product.category === filters.category; const matchBrand = filters.brand.length === 0 ? true : filters.brand.includes(product.brand); const matchPrice = product.price >= filters.priceRange[0] && product.price <= filters.priceRange[1]; return matchKeyword && matchCategory && matchBrand && matchPrice; }); }, [filters, products]); // 4. 事件处理函数用useCallback缓存 const handleFilterChange = useCallback((key: keyof FilterParams, value: any) => { setFilters(prev => ({ ...prev, [key]: value })); }, []); const resetFilters = useCallback(() => { setFilters({ keyword: '', category: 'all', brand: [], priceRange: [0, 10000], }); }, []);
优化 2:交互体验升级
// 1. 价格筛选改为滑块组件(更直观) import { Slider } from 'antd'; // 价格筛选区域替换为: <div style={{ width: '250px' }}> <div style={{ marginBottom: '8px', fontSize: '14px', color: '#666' }}> 价格范围:¥{filters.priceRange[0]} - ¥{filters.priceRange[1]} </div> <Slider range min={0} max={10000} step={10} value={filters.priceRange} onChange={value => handleFilterChange('priceRange', value as [number, number])} style={{ width: '100%' }} /> </div> // 2. 品牌筛选添加"全选/取消全选" <div style={{ flex: 1, minWidth: '250px' }}> <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: '8px' }}> <span style={{ fontSize: '14px', color: '#666' }}>选择品牌</span> <Checkbox checked={filters.brand.length === brandOptions.length && brandOptions.length > 0} indeterminate={filters.brand.length > 0 && filters.brand.length < brandOptions.length} onChange={e => { const newBrands = e.target.checked ? brandOptions.map(b => b.value) : []; handleFilterChange('brand', newBrands); }} > 全选 </Checkbox> </div> <div style={{ display: 'flex', flexWrap: 'wrap', gap: '8px' }}> {brandOptions.map(brand => ( <Checkbox key={brand.value} checked={filters.brand.includes(brand.value)} onChange={e => { const newBrands = e.target.checked ? [...filters.brand, brand.value] : filters.brand.filter(b => b !== brand.value); handleFilterChange('brand', newBrands); }} > {brand.label} </Checkbox> ))} </div> </div> // 3. 添加排序功能 const [sortType, setSortType] = useState<'default' | 'priceAsc' | 'priceDesc' | 'salesDesc'>('default'); // 排序后的商品数据 const sortedProducts = useMemo(() => { const copy = [...filteredProducts]; switch (sortType) { case 'priceAsc': return copy.sort((a, b) => a.price - b.price); case 'priceDesc': return copy.sort((a, b) => b.price - a.price); case 'salesDesc': return copy.sort((a, b) => b.sales - a.sales); default: return copy.sort((a, b) => a.id - b.id); } }, [filteredProducts, sortType]); // 排序选择器(添加在筛选区域下方) <div style={{ display: 'flex', justifyContent: 'flex-end', marginBottom: '16px' }}> <Select placeholder="选择排序方式" value={sortType} onChange={setSortType} style={{ width: '200px' }} > <Select.Option value="default">默认排序</Select.Option> <Select.Option value="priceAsc">价格从低到高</Select.Option> <Select.Option value="priceDesc">价格从高到低</Select.Option> <Select.Option value="salesDesc">销量从高到低</Select.Option> </Select> </div> // 分页数据改为基于排序后的数据 const paginatedProducts = sortedProducts.slice( (currentPage - 1) * pageSize, currentPage * pageSize );
优化 3:扩展性升级(支持自定义配置)
// 1. 定义组件Props,支持外部传入配置 interface ProductListProps { pageSize?: number; maxPrice?: number; placeholder?: string; onProductClick?: (product: Product) => void; } // 2. 组件接收Props并设置默认值 const ProductList: React.FC<ProductListProps> = ({ pageSize = 12, maxPrice = 10000, placeholder = "搜索商品名称...", onProductClick, }) => { // 3. 价格筛选最大值使用外部传入的maxPrice const [filters, setFilters] = useState<FilterParams>({ keyword: '', category: 'all', brand: [], priceRange: [0, maxPrice], }); // 4. 商品卡片添加点击事件 <ProductCard key={product.id} product={product} onClick={() => onProductClick?.(product)} /> }; // 5. 暴露默认导出 export default ProductList;
优化后组件核心优势
- 性能更优:通过memo、useMemo、useCallback减少重复渲染,筛选逻辑惰性计算;
- 体验更好:滑块价格筛选、品牌全选、排序功能,操作更直观高效;
- 扩展性强:支持自定义分页大小、价格上限、搜索占位符,以及商品点击回调;
- 视觉更美观:商品卡片 hover 动画、布局响应式适配,适配不同屏幕尺寸。