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

React Native中FlatList的onEndReached无法正常加载更多数据

问题根因

现有代码存在三个核心逻辑错误,才会出现要么重复触发、要么完全不触发的异常:

  1. 加载判断条件写反:onEndReached里写的是isLoading && 执行加载,逻辑完全颠倒——加载锁的作用是正在加载时阻止重复触发,应该是!isLoading的时候才允许执行加载逻辑,条件写反之后,只要isLoading为false(绝大多数场景的默认状态),判断条件永远不成立,触底回调自然完全不会执行加载逻辑。
  2. 加载状态流转完全错误:isLoading状态从来没有在加载发起时被设为true,反而在loadMore函数里直接设为false,等于加载锁从一开始就是失效的;同时没有做「是否已加载完全部数据」的判断,只要触达阈值就会执行数据拼接,列表更新后高度不足会反复判定触底,自然会重复触发多次。
  3. 逻辑零散未闭环:定义了loadMore函数但根本没在触底回调里调用,stopFetchMore变量定义后也未实际使用,触底后直接同步执行setData,完全没有异步加载的锁机制;再加上onEndReachedThreshold设为0.3(距离底部还有30%列表高度就触发),首次渲染如果列表内容没撑满屏幕,还会自动触发一次触底回调,进一步加剧重复触发问题。
修复方案

按照分页加载的标准状态流调整即可,步骤如下:

  • 定义三个核心状态,设置正确初始值:
    • data:存储列表渲染数据,初始为第一页加载的6条数据
    • isLoading:加载锁,初始值为false,标记当前是否正在请求分页数据
    • isLastPage:末页标记,初始值为false,标记是否已经加载完所有分页数据,避免无意义的请求
  • 修正触底回调判断逻辑:只有当isLoading === false且isLastPage === false时,才执行加载更多逻辑
  • 修正loadMore执行流程:加载开始第一时间把isLoading设为true上锁 -> 请求下一页数据 -> 拼接新数据到原列表 -> 判断返回的新数据长度是否小于单页条数,若是则把isLastPage设为true -> 所有操作完成后把isLoading设为false释放锁
  • 把onEndReachedThreshold调整为0.15左右,避免过早触发导致重复调用
  • 给ListFooterComponent增加不同状态的展示:加载中显示loading提示,到末页显示「没有更多数据」提示
修复后可直接参考的代码示例
import React, { useState, useCallback } from 'react';
import { FlatList, View, Text, ActivityIndicator, Dimensions } from 'react-native';

const { height } = Dimensions.get('window');
// 单页加载条数,和后端接口约定一致
const PAGE_SIZE = 6;

export default function TopicList() {
  const [data, setData] = useState([]); // 初始值替换为第一页加载的6条数据
  const [isLoading, setIsLoading] = useState(false);
  const [isLastPage, setIsLastPage] = useState(false);

  // 替换为实际的分页请求逻辑,示例为模拟异步请求
  const fetchPageData = async (pageNum) => {
    return new Promise(resolve => {
      setTimeout(() => {
        // 替换为接口返回的分页数据topicsPagination
        const newList = Array(PAGE_SIZE).fill(0).map((_,i) => ({
          id: `${pageNum}-${i}`, 
          content: `条目${pageNum}-${i}`
        }));
        resolve(newList);
      }, 800)
    })
  };

  const loadMore = useCallback(async () => {
    // 上锁,阻止重复触发
    setIsLoading(true);
    try {
      const currentPage = Math.ceil(data.length / PAGE_SIZE) + 1;
      const newData = await fetchPageData(currentPage);
      // 拼接新数据
      setData(prev => [...prev, ...newData]);
      // 判断是否到最后一页:返回数据条数小于单页条数说明没有更多了
      if (newData.length < PAGE_SIZE) {
        setIsLastPage(true);
      }
    } catch (err) {
      console.log('加载失败', err);
    } finally {
      // 不管成功失败都释放锁
      setIsLoading(false);
    }
  }, [data.length]);

  // 列表底部组件
  const renderFooter = useCallback(() => {
    if (isLoading) {
      return <View style={{padding: 16, alignItems: 'center'}}>
        <ActivityIndicator />
        <Text style={{marginTop: 8, fontSize: 12, color: '#999'}}>加载中...</Text>
      </View>
    }
    if (isLastPage) {
      return <View style={{padding: 16, alignItems: 'center'}}>
        <Text style={{fontSize: 12, color: '#999'}}>已经到底啦</Text>
      </View>
    }
    return null;
  }, [isLoading, isLastPage]);

  return (
    <FlatList
      data={data}
      renderItem={({item, index}) => (
        // 替换为实际的列表项渲染逻辑
        <View style={{
          width: '48%', 
          padding: 8, 
          height: 120, 
          backgroundColor: '#f5f5f5', 
          margin: '1%', 
          borderRadius: 8
        }}>
          <Text>{item.content}</Text>
        </View>
      )}
      numColumns={2}
      // 不要用数组index作为key,用item唯一id避免渲染异常
      keyExtractor={(item) => item.id}
      style={{marginTop: height * 0.01}}
      showsVerticalScrollIndicator={false}
      onEndReached={() => {
        // 核心判断:没在加载、没到末页才触发加载
        if (!isLoading && !isLastPage) {
          loadMore();
        }
      }}
      onEndReachedThreshold={0.15}
      ListFooterComponent={renderFooter}
      bounces={true}
    />
  )
}
额外注意点
  • keyExtractor不要直接用数组index作为key,否则列表动态追加数据时容易出现渲染错乱、onEndReached异常触发的问题,要用后端返回的每条数据的唯一id作为key。
  • 如果使用函数组件,把loadMore、renderFooter、renderItem这类回调函数用useCallback包裹,避免每次渲染生成新函数引用,导致FlatList不必要的重渲染,也能减少触底回调的异常触发。
  • 不要在onEndReached里直接写同步setData逻辑,所有数据加载走异步流程,靠isLoading状态做拦截,从根源上避免重复调用。

内容的提问来源于stack exchange,提问作者deepanshi gupta

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.27 02:36:14