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

React Native FlatList分页:如何从最新页反向加载API数据

React Native反向分页加载实现(从最大页码往旧数据加载)

问题描述

在React Native开发中使用FlatList实现API分页加载时,遇到API的分页逻辑为:page 1存储最旧数据,最新数据位于最大页码(如10000)处。现有代码默认从page 1往后加载,需修改为从最大页码开始,依次加载page 9999、9998……直到page 1的反向分页逻辑。

API返回的links结构示例:

"links":{
    "self":"https://api.hackerone.com/v1/hackers/me/reports?page%5Bnumber%5D=1&page%5Bsize%5D=100",
    "prev":"https://api.hackerone.com/v1/hackers/me/reports?page%5Bnumber%5D=0&page%5Bsize%5D=100",
    "next":"https://api.hackerone.com/v1/hackers/me/reports?page%5Bnumber%5D=2&page%5Bsize%5D=100"
}

现有正向分页加载代码:

const initialPage = `https://censored.xxx/withpagination/`;  

const [data, setData] = useState<any[]>([]);
const [isLoading, setIsLoading] = useState(false);
const [refreshing, setRefreshing] = useState(false);
const [nextPage, setNextPage] = useState('');

const { width } = useWindowDimensions();

const fetchReportsList = async (url: string) => {
    if (isLoading) {
      return;
    }

    setIsLoading(true);
    fetch(url, {
        method: 'GET',
        headers: {
            "Authorization": "Basic <bearer-token>",
            "Content-Type": "application/json"
        }
    })
    .then( res => res.json() )
    .then( res => {
      setData((existingItems) => {
        return [...existingItems, ...res.data];
      });
      setNextPage(res.links.next);
      setIsLoading(false);
    }).catch((error => {
      console.log("Error, error", error);
    }));
}

useEffect(() => {
  fetchReportsList(initialPage);
}, []);

解决方案

要实现反向分页,需调整三个核心逻辑:获取最大页码、反向请求分页数据、调整数据合并顺序,同时适配FlatList的滚动触发时机。

1. 先获取最大页码

首先从API获取总页数(或最大页码),通常API会在meta或pagination字段返回该信息。如果没有,则通过递归试探较大页码来确定:

情况1:API返回总页数

// 新增状态存储当前页码和最大页码
const [currentPage, setCurrentPage] = useState<number>(0);
const [maxPage, setMaxPage] = useState<number>(0);

// 先请求总页数,再从最大页码开始加载
const fetchMaxPage = async () => {
  try {
    const res = await fetch(initialPage, {
      method: 'GET',
      headers: {
        "Authorization": "Basic <bearer-token>",
        "Content-Type": "application/json"
      }
    });
    const data = await res.json();
    // 假设API返回的meta中包含total_pages字段
    const totalPages = data.meta.total_pages;
    setMaxPage(totalPages);
    setCurrentPage(totalPages);
    // 直接请求最大页码的数据
    fetchReportsList(`https://censored.xxx/withpagination/?page[number]=${totalPages}&page[size]=100`);
  } catch (error) {
    console.log("Error fetching max page", error);
  }
};

// 替换初始请求为获取最大页码
useEffect(() => {
  fetchMaxPage();
}, []);

情况2:API不返回总页数

const [currentPage, setCurrentPage] = useState<number>(0);

// 递归试探最大页码
const findMaxPage = async (page: number = 10000) => {
  try {
    const res = await fetch(`https://censored.xxx/withpagination/?page[number]=${page}&page[size]=100`, {
      headers: {
        "Authorization": "Basic <bearer-token>",
        "Content-Type": "application/json"
      }
    });
    const resData = await res.json();
    if (resData.data.length > 0) {
      setCurrentPage(page);
      setData(resData.data);
      // 设置下一个请求的页码(当前页码-1)
      setNextPage(page > 1 ? `https://censored.xxx/withpagination/?page[number]=${page - 1}&page[size]=100` : '');
      return;
    }
    // 当前页码无数据,递归试探前一页
    findMaxPage(page - 1);
  } catch (error) {
    console.log("Error finding max page", error);
  }
};

useEffect(() => {
  findMaxPage();
}, []);

2. 修改分页请求逻辑

不再依赖links.next,而是通过当前页码减1请求前一页(旧数据),同时调整数据合并顺序(旧数据追加到列表末尾,保持最新数据在顶部):

const fetchReportsList = async (url: string) => {
  if (isLoading) return;
  setIsLoading(true);
  
  try {
    const res = await fetch(url, {
      method: 'GET',
      headers: {
        "Authorization": "Basic <bearer-token>",
        "Content-Type": "application/json"
      }
    });
    const resData = await res.json();

    // 旧数据追加到现有列表末尾,确保最新数据始终在顶部
    setData(prev => [...prev, ...resData.data]);

    const newPage = currentPage - 1;
    setCurrentPage(newPage);
    // 当页码大于1时,生成下一个请求的URL
    setNextPage(newPage >= 1 ? `https://censored.xxx/withpagination/?page[number]=${newPage}&page[size]=100` : '');
    setIsLoading(false);
  } catch (error) {
    console.log("Error fetching reports", error);
    setIsLoading(false);
  }
}

3. 适配FlatList滚动触发逻辑

用户滚动到底部时加载更早的旧数据,onEndReached逻辑保持不变,仅需判断nextPage是否存在:

<FlatList
  data={data}
  renderItem={({item}) => <YourReportItem item={item} />}
  keyExtractor={(item) => item.id.toString()}
  onEndReached={() => nextPage && fetchReportsList(nextPage)}
  onEndReachedThreshold={0.1}
  ListFooterComponent={isLoading ? <ActivityIndicator size="small" /> : null}
/>

4. 下拉刷新逻辑(可选)

下拉刷新时重新请求最大页码的最新数据,重置列表:

const handleRefresh = async () => {
  setRefreshing(true);
  try {
    // 重新获取最大页码
    const res = await fetch(initialPage, {
      headers: {
        "Authorization": "Basic <bearer-token>",
        "Content-Type": "application/json"
      }
    });
    const data = await res.json();
    const totalPages = data.meta.total_pages;
    // 请求最新页码的数据
    const latestRes = await fetch(`https://censored.xxx/withpagination/?page[number]=${totalPages}&page[size]=100`, {
      headers: {
        "Authorization": "Basic <bearer-token>",
        "Content-Type": "application/json"
      }
    });
    const latestData = await latestRes.json();
    // 重置列表和分页状态
    setData(latestData.data);
    setMaxPage(totalPages);
    setCurrentPage(totalPages);
    setNextPage(totalPages > 1 ? `https://censored.xxx/withpagination/?page[number]=${totalPages -1}&page[size]=100` : '');
  } catch (error) {
    console.log("Error refreshing", error);
  }
  setRefreshing(false);
};

// 在FlatList中添加下拉刷新控件
<FlatList
  // ...其他属性
  refreshControl={
    <RefreshControl
      refreshing={refreshing}
      onRefresh={handleRefresh}
    />
  }
/>

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.23 18:42:36