React Native实现同分类商品横向FlatList/SectionList展示
React Native 分类横向商品列表实现方案
疑问1:是否需要按分类维度分组商品数据?
- 必须分组。要实现「分类标题+对应分类横向商品列表」的区块结构,首先需要把接口返回的平铺商品列表,按照商品自带的
category字段聚合为分组结构。 - 分组实现逻辑:用数组
reduce方法遍历原始商品列表,以分类名为key聚合同分类商品,最终输出适配SectionList的分组格式:[{ title: '分类名', data: [[该分类下所有商品]] }, ...],每个分组的data用二维数组包裹,保证每个分类只渲染一次横向列表。 - 核心分组代码:
// 接口拿到products后执行分组 const grouped = products.reduce((sections, product) => { const existSection = sections.find(sec => sec.title === product.category); if (existSection) { existSection.data[0].push(product); } else { sections.push({ title: product.category, data: [[product]] }); } return sections; }, []); // 如需自定义分类排序,可在此处调整grouped数组顺序
疑问2:选择FlatList还是SectionList更优?
该场景优先选择SectionList作为外层竖向滚动容器,原因如下:
- SectionList原生支持分组区块的头部渲染,不需要手动处理分组标题和商品列表的位置映射,代码逻辑更简洁
- 内置分组级别的懒加载、内存回收机制,比自行用普通FlatList嵌套实现分组的性能更好,长列表下不会出现明显卡顿
- 支持开箱即用的粘性头部、分组下拉刷新等扩展能力,后续迭代不需要额外改造
注:每个分组内部的横向商品流,使用开启horizontal属性的FlatList实现即可,不会和外层竖向SectionList产生滚动冲突。
修正后的可运行代码
首先修复原有代码的已知问题:
- 给useEffect添加空依赖数组,避免每次渲染都重复触发接口请求
- 新增商品分组逻辑
- 替换原有双列FlatList为SectionList结构,每个区块内渲染横向商品FlatList
import { View, TouchableOpacity, FlatList, ActivityIndicator, SectionList, Text } from 'react-native'; import { useState, useEffect } from 'react'; import { MaterialIcons, Feather } from '@expo/vector-icons'; // 按自身项目的图标引入路径调整 import ProductCard from './ProductCard'; // 按自身项目的组件路径调整 import { COLORS } from './constants'; // 按自身项目的常量路径调整 const HomeScreen = ({ navigation }) => { const [groupedSections, setGroupedSections] = useState<{title: string, data: any[]}[]>([]); const [loading, setLoading] = useState(true); useEffect(() => { setLoading(true); fetch("https://dummyjson.com/products") .then((response) => response.json()) .then(({ products }) => { // 执行分组逻辑 const grouped = products.reduce((sections, product) => { const existSection = sections.find(sec => sec.title === product.category); if (existSection) { existSection.data[0].push(product); } else { sections.push({ title: product.category, data: [[product]] }); } return sections; }, []); setGroupedSections(grouped); setLoading(false); }) .catch((error) => { console.log(error); setLoading(false); }); }, []); // 补全依赖数组,避免重复请求 if (loading) { return ( <View style={{ flex: 1, justifyContent: "center", alignItems: "center", }} > <ActivityIndicator size="large" /> </View> ) } return ( <View style={{ flex: 1, backgroundColor: COLORS.backgroundLight, marginTop: 20, }} > {/* 顶部导航栏 */} <View style={{ width: "100%", flexDirection: "row", justifyContent: "space-between", padding: 16, }} > <TouchableOpacity onPress={() => navigation.openDrawer()}> <MaterialIcons name="menu" size={30} color="black" /> </TouchableOpacity> <TouchableOpacity onPress={() => navigation.navigate("CartScreen")}> <Feather name="shopping-cart" size={30} color="black" /> </TouchableOpacity> </View> {/* 分组商品列表 */} <SectionList sections={groupedSections} keyExtractor={(item, index) => index.toString()} renderSectionHeader={({ section: { title } }) => ( <Text style={{ fontSize: 18, fontWeight: 'bold', padding: 16, textTransform: 'capitalize', backgroundColor: COLORS.backgroundLight }}> {title} </Text> )} renderItem={({ item }) => ( <FlatList data={item} horizontal showsHorizontalScrollIndicator={false} contentContainerStyle={{ paddingHorizontal: 16, gap: 12, paddingBottom: 24 }} keyExtractor={(product) => product.id.toString()} renderItem={({ item: product }) => ( <ProductCard title={product.title} price={product.price} image={product.images[0]} onPress={() => navigation.navigate("ProductDetails", product)} /> )} /> )} stickySectionHeadersEnabled={false} // 需要分类吸顶可改为true /> </View> ); }; export default HomeScreen;
额外优化提示
- 低版本React Native不支持gap样式,可给横向FlatList添加
ItemSeparatorComponent配置商品卡片间距,兼容性更好 - 如果分类数量多、商品量大,可以给外层SectionList配置
windowSize参数优化内存占用 - 可根据设计需求调整分类标题样式、横向列表内边距、商品卡片尺寸
内容的提问来源于stack exchange,提问作者Hamady
相关产品推荐
相关产品推荐

