React Native中如何实现FlatList数据渐进式加载?
实现React Native FlatList渐进式加载(分页渲染)
先修复现有代码核心bug
原useHttpClient中res.json()未加await,导致返回Promise而非解析后的数据,直接引发数据处理异常:
// 修改useHttpClient的sendRequest方法 const sendRequest = useCallback( async (URL, method = "GET", body = null, headers = {}, credentials = "include") => { setIsLoading(true); try { const res = await fetch(URL, { method, body, headers, credentials }); const resData = await res.json(); // 必须添加await! if (!res.ok) { throw new Error(resData.error || '请求失败'); } setIsLoading(false); return resData; } catch (error) { setIsLoading(false); setError(error.message); throw error; // 抛出错误让调用方捕获处理 } }, [] );
后端适配分页接口
需要后端支持分页参数(如page页码、limit每页条数),并返回分页元数据(如totalPages总页数),接口示例:GET /api/location?lng=37.7749295&lat=-122.4194155&page=1&limit=10
返回结构示例:
{ "results": [...], "totalPages": 5, "totalCount": 48 }
前端实现分页加载逻辑
修改RestaurantsScreen,添加分页状态管理和滚动触底加载:
import { useState, useEffect, useContext, memo } from "react"; import { SafeAreaView, Text, View, Button } from "react-native"; import { RestaurantsContext } from "../contexts/RestaurantsContext"; import { useHttpClient } from "../hooks/useHttpClient"; import RestaurantList from "../components/RestaurantList"; import Searchbar from "../components/Searchbar"; import Spacer from "../components/Spacer"; import Center from "../components/Center"; // 用memo优化渲染项,避免不必要重渲染 const renderItemComponent = memo(({ item }) => { return ( <Spacer dir="vertical" size="small"> <RestaurantCard restaurant={item} /> </Spacer> ); }); export const RestaurantsScreen = () => { const { isLoading, error, clearError, sendRequest } = useHttpClient(); const { restaurants, setRestaurants } = useContext(RestaurantsContext); const [page, setPage] = useState(1); const [hasMore, setHasMore] = useState(true); const [isLoadingMore, setIsLoadingMore] = useState(false); // 加载指定页码的数据 const fetchPage = async (currentPage) => { if (currentPage > 1) setIsLoadingMore(true); try { const URL = `http://localhost:3000/api/location?lng=37.7749295&lat=-122.4194155&page=${currentPage}&limit=10`; const { results, totalPages } = await sendRequest(URL); const mappedResult = results.map((restaurant) => ({ ...restaurant, isOpenNow: restaurant.opening_hours?.open_now, isClosedTemporarily: restaurant.business_status === "CLOSED_TEMPORARILY", })); const loadedRestaurants = camelize(mappedResult); // 合并或替换现有数据 if (currentPage === 1) { setRestaurants(loadedRestaurants); } else { setRestaurants(prev => [...prev, ...loadedRestaurants]); } // 判断是否还有更多数据 setHasMore(currentPage < totalPages); } catch (err) { console.error('加载失败:', err); } finally { if (currentPage > 1) setIsLoadingMore(false); } }; // 初始加载第一页 useEffect(() => { fetchPage(1); }, []); // 页码变化时加载对应页 useEffect(() => { if (page > 1) { fetchPage(page); } }, [page]); // 触底加载下一页 const loadMore = () => { if (hasMore && !isLoadingMore && !isLoading) { setPage(prev => prev + 1); } }; return ( <SafeAreaView style={{flex: 1}}> <View style={{paddingHorizontal: 16, paddingVertical: 8}}> <Searchbar placeholder="Search ..." /> </View> {/* 错误提示 */} {error && ( <Center style={{padding: 16}}> <Text style={{color: '#ff3b30'}}>{error}</Text> <Button title="重试" onPress={() => fetchPage(page)} /> <Button title="清除错误" onPress={clearError} /> </Center> )} {/* 初始加载状态 */} {isLoading && restaurants.length === 0 && ( <Center style={{flex: 1}}> <Text>Loading Restaurants ... </Text> </Center> )} {/* 空状态 */} {!isLoading && restaurants.length === 0 && !error && ( <Center style={{flex: 1}}> <Text>No Restaurants Available</Text> </Center> )} {/* 列表渲染 */} {restaurants.length > 0 && ( <RestaurantList data={restaurants} renderItem={renderItemComponent} keyExtractor={(item) => item.id || item.name} // 优先用唯一ID,避免名称重复 initialNumToRender={10} onEndReached={loadMore} onEndReachedThreshold={0.1} // 滚动到距离底部10%时触发加载 ListFooterComponent={isLoadingMore ? ( <Center style={{paddingVertical: 16}}> <Text>加载更多...</Text> </Center> ) : null} // 若列表项高度固定,添加getItemLayout提升性能 getItemLayout={(data, index) => ({ length: 200, // 替换为你的RestaurantCard实际高度 offset: 200 * index, index })} /> )} </SafeAreaView> ); };
关键优化说明
- 分页状态管控:通过
page、hasMore、isLoadingMore避免重复请求,控制加载流程 - 状态更新规范:使用context提供的
setRestaurants方法更新数据,确保组件触发重渲染 - 性能优化:用
React.memo包裹渲染项、添加getItemLayout减少FlatList重计算,提升滚动流畅度 - 错误处理:完善捕获和重试逻辑,避免网络问题导致应用崩溃
- 用户体验:区分初始加载和加载更多的状态反馈,避免用户困惑
内容的提问来源于stack exchange,提问作者Mahdyar
相关产品推荐
相关产品推荐

