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

React Native嵌套FlatList实现分段加载更多方案咨询

实现跨类型分批滚动加载方案

首先,两层FlatList的结构很难实现这种跨类型的分批加载逻辑,建议改用单个FlatList统一管理所有渲染项,通过状态控制分批加载的进度,最后在所有数据加载完成后显示后续3个组件。以下是具体实现步骤和代码示例:

核心思路

  1. 将原API数据拆解为可分批提取的结构,区分「类型标题」和「详情项」两种渲染单元
  2. 通过状态跟踪当前加载的类型索引、已加载的详情数量、已渲染的列表项
  3. 利用FlatList的onEndReached事件触发滚动加载,按批次(每批5条)加载同类型剩余数据,完成后自动切换到下一个类型
  4. 所有数据加载完成后,渲染后续3个组件

代码实现

import React, { useState, useEffect } from 'react';
import { View, Text, FlatList, Image, ActivityIndicator } from 'react-native';

// 模拟API返回数据
const API_DATA = [
  { 
    type:'type 1', 
    detail:[
      { title:'标题1-1', pic:'https://example.com/pic1.jpg', time:'2024-01-01' },
      { title:'标题1-2', pic:'https://example.com/pic2.jpg', time:'2024-01-02' },
      { title:'标题1-3', pic:'https://example.com/pic3.jpg', time:'2024-01-03' },
      { title:'标题1-4', pic:'https://example.com/pic4.jpg', time:'2024-01-04' },
      { title:'标题1-5', pic:'https://example.com/pic5.jpg', time:'2024-01-05' },
      { title:'标题1-6', pic:'https://example.com/pic6.jpg', time:'2024-01-06' },
      { title:'标题1-7', pic:'https://example.com/pic7.jpg', time:'2024-01-07' }
    ] 
  },
  { 
    type:'type 2', 
    detail:[
      { title:'标题2-1', pic:'https://example.com/pic8.jpg', time:'2024-01-08' },
      { title:'标题2-2', pic:'https://example.com/pic9.jpg', time:'2024-01-09' },
      { title:'标题2-3', pic:'https://example.com/pic10.jpg', time:'2024-01-10' }
    ] 
  },
  { 
    type:'type 3', 
    detail:[
      { title:'标题3-1', pic:'https://example.com/pic11.jpg', time:'2024-01-11' },
      { title:'标题3-2', pic:'https://example.com/pic12.jpg', time:'2024-01-12' }
    ] 
  }
];

const FinalComponent1 = () => <View style={{padding:20, backgroundColor:'#f0f0f0'}}><Text>最终组件1</Text></View>;
const FinalComponent2 = () => <View style={{padding:20, backgroundColor:'#e0e0e0'}}><Text>最终组件2</Text></View>;
const FinalComponent3 = () => <View style={{padding:20, backgroundColor:'#d0d0d0'}}><Text>最终组件3</Text></View>;

const LoadableList = () => {
  // 当前加载的类型索引
  const [currentTypeIndex, setCurrentTypeIndex] = useState(0);
  // 当前类型已加载的详情数量
  const [currentLoadedCount, setCurrentLoadedCount] = useState(0);
  // 已渲染的所有列表项(包含类型标题和详情项)
  const [displayedItems, setDisplayedItems] = useState([]);
  // 是否还有更多数据可加载
  const [hasMore, setHasMore] = useState(true);
  // 是否显示最后的3个组件
  const [showFinalComponents, setShowFinalComponents] = useState(false);
  // 加载中状态(避免重复触发)
  const [loading, setLoading] = useState(false);

  // 初始化加载:先加载第一个类型的标题+前5条详情
  useEffect(() => {
    initLoad();
  }, []);

  // 初始加载逻辑
  const initLoad = () => {
    if (API_DATA.length === 0) {
      setHasMore(false);
      setShowFinalComponents(true);
      return;
    }
    const firstType = API_DATA[0];
    // 添加类型标题
    const initialItems = [{ type: 'header', content: firstType.type }];
    // 取前5条详情
    const firstBatch = firstType.detail.slice(0, 5);
    initialItems.push(...firstBatch.map(item => ({ type: 'detail', content: item })));
    setDisplayedItems(initialItems);
    setCurrentLoadedCount(5);
    // 判断是否还有更多数据
    setHasMore(firstType.detail.length > 5 || API_DATA.length > 1);
  };

  // 加载更多逻辑
  const loadMore = () => {
    if (loading || !hasMore) return;
    setLoading(true);

    // 模拟API请求延迟(实际项目中替换为真实接口请求)
    setTimeout(() => {
      const currentType = API_DATA[currentTypeIndex];
      let newItems = [];
      let newLoadedCount = currentLoadedCount;
      let newTypeIndex = currentTypeIndex;
      let newHasMore = true;

      // 检查当前类型是否还有未加载的详情
      if (currentLoadedCount < currentType.detail.length) {
        // 加载下一批5条详情
        const nextBatch = currentType.detail.slice(currentLoadedCount, currentLoadedCount + 5);
        newItems = nextBatch.map(item => ({ type: 'detail', content: item }));
        newLoadedCount += nextBatch.length;
        // 判断当前类型是否加载完
        if (newLoadedCount >= currentType.detail.length) {
          // 当前类型加载完,检查是否有下一个类型
          if (currentTypeIndex + 1 < API_DATA.length) {
            newTypeIndex += 1;
            const nextType = API_DATA[newTypeIndex];
            // 添加下一个类型的标题
            newItems.push({ type: 'header', content: nextType.type });
            // 加载下一个类型的前5条详情
            const nextTypeFirstBatch = nextType.detail.slice(0, 5);
            newItems.push(...nextTypeFirstBatch.map(item => ({ type: 'detail', content: item })));
            newLoadedCount = nextTypeFirstBatch.length;
            // 判断下一个类型是否还有更多数据
            newHasMore = nextType.detail.length > 5 || newTypeIndex + 1 < API_DATA.length;
          } else {
            // 所有类型都加载完了
            newHasMore = false;
            setShowFinalComponents(true);
          }
        } else {
          // 当前类型还有剩余数据
          newHasMore = true;
        }
      } else {
        // 当前类型已加载完,切换到下一个类型
        if (currentTypeIndex + 1 < API_DATA.length) {
          newTypeIndex += 1;
          const nextType = API_DATA[newTypeIndex];
          newItems.push({ type: 'header', content: nextType.type });
          const nextTypeFirstBatch = nextType.detail.slice(0, 5);
          newItems.push(...nextTypeFirstBatch.map(item => ({ type: 'detail', content: item })));
          newLoadedCount = nextTypeFirstBatch.length;
          newHasMore = nextType.detail.length > 5 || newTypeIndex + 1 < API_DATA.length;
        } else {
          newHasMore = false;
          setShowFinalComponents(true);
        }
      }

      // 更新状态
      setDisplayedItems(prev => [...prev, ...newItems]);
      setCurrentTypeIndex(newTypeIndex);
      setCurrentLoadedCount(newLoadedCount);
      setHasMore(newHasMore);
      setLoading(false);
    }, 1000);
  };

  // 渲染列表项
  const renderItem = ({ item }) => {
    if (item.type === 'header') {
      return (
        <Text style={{ fontSize: 18, fontWeight: 'bold', padding: 10, backgroundColor: '#f5f5f5' }}>
          {item.content}
        </Text>
      );
    } else if (item.type === 'detail') {
      const detail = item.content;
      return (
        <View style={{ padding: 10, borderBottomWidth: 1, borderBottomColor: '#eee' }}>
          <Text style={{ fontSize: 16, marginBottom: 5 }}>{detail.title}</Text>
          <Image 
            source={{ uri: detail.pic }} 
            style={{ width: '100%', height: 150, marginBottom: 5 }} 
            resizeMode="cover"
          />
          <Text style={{ fontSize: 12, color: '#666' }}>{detail.time}</Text>
        </View>
      );
    }
    return null;
  };

  // 渲染加载更多提示
  const renderFooter = () => {
    if (!hasMore) return null;
    return (
      <View style={{ padding: 20 }}>
        <ActivityIndicator size="small" color="#000" />
      </View>
    );
  };

  return (
    <View style={{ flex: 1 }}>
      <FlatList
        data={displayedItems}
        renderItem={renderItem}
        keyExtractor={(item, index) => `${item.type}-${index}`}
        onEndReached={loadMore}
        onEndReachedThreshold={0.1}
        ListFooterComponent={renderFooter}
      />
      {showFinalComponents && (
        <View>
          <FinalComponent1 />
          <FinalComponent2 />
          <FinalComponent3 />
        </View>
      )}
    </View>
  );
};

export default LoadableList;

关键细节说明

  • 数据结构转换:将原API的分组数据拆分为「header(类型标题)」和「detail(详情项)」两种类型的渲染单元,统一存入displayedItems数组,方便单个FlatList渲染
  • 加载状态控制:通过currentTypeIndex、currentLoadedCount跟踪当前加载进度,loading状态避免滚动时重复触发加载
  • 滚动加载触发:利用onEndReached和onEndReachedThreshold(设置为0.1表示当列表滚动到距离底部10%时触发加载)实现自动加载
  • 最终组件显示:当所有类型的所有详情项都加载完成后,showFinalComponents设为true,渲染后续3个组件

另外注意你原代码中的拼写错误:rebderItem应该改为renderItem,Image的source属性写法应为{ uri: item.pic }。

内容的提问来源于stack exchange,提问作者m.akbari

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.13 03:40:34