You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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产生滚动冲突。

修正后的可运行代码

首先修复原有代码的已知问题:

  1. 给useEffect添加空依赖数组,避免每次渲染都重复触发接口请求
  2. 新增商品分组逻辑
  3. 替换原有双列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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.28 08:36:27