FlatList 是React Native开发中的重点,也是支撑业务发展经常会用的组件,经常分页以及加载显示等常用功能的封装是每个业务必备的功能
以下是简单的封装:
import React, { useState, useEffect } from 'react';
import { FlatList, RefreshControl } from 'react-native';
/**
* props:
* renderItem: (item: T, index: number) => React.ReactNode
* keyExtractor: (item: T, index: number) => string
* data: T[]
* onFetchData: (page: number) => Promise<T[]>
* initialPage: number
* pageSize: number
* refreshControlProps?: RefreshControlProps
*/
function PaginatedFlatList({ renderItem, keyExtractor, data, onFetchData, initialPage = 1, pageSize = 10, refreshControlProps }) {
// 初始化 state
const [page, setPage] = useState(initialPage);
const [isLoading, setIsLoading] = useState(false);
const [isRefreshing, setIsRefreshing] = useState(false);
const [hasMoreData, setHasMoreData] = useState(true);
// 当 page 发生变化时,重新获取数据
useEffect(() => {
fetchData();
}, [page]);
// 获取数据
const fetchData = async () => {
// 判断是否需要分页获取
if (!hasMoreData || isLoading) {
return;
}
setIsLoading(true);
try {
const newData = await onFetchData(page);
if (newData.length < pageSize) {
// 如果返回的数据少于 pageSize,说明没有更多数据了
setHasMoreData(false);
}
// 将新数据添加到现有数据后面
setData([...data, ...newData]);
} catch (error) {
console.log(error);
}
setIsLoading(false);
};
// 刷新数据
const refreshData = async () => {
if (isRefreshing) {
return;
}
setIsRefreshing(true);
setPage(initialPage);
setHasMoreData(true);
try {
const newData = await onFetchData(initialPage);
setData(newData);
} catch (error) {
console.log(error);
}
setIsRefreshing(false);
};
// 渲染每个 item
const renderItemWithIndex = ({ item, index }) => renderItem(item, index);
// 渲染 FlatList
return (
<FlatList
data={data}
renderItem={renderItemWithIndex}
keyExtractor={keyExtractor}
refreshControl={
<RefreshControl
refreshing={isRefreshing}
onRefresh={refreshData}
{...refreshControlProps}
/>
}
onEndReachedThreshold={0.1}
onEndReached={fetchData}
ListFooterComponent={ isLoading ? (
<ActivityIndicator size="large" />
) : (
!hasMoreData && (
<View>
{!data.length ? noDataComponent : null}
{error ? errorComponent : null}
</View>
)
)
}
/>
);
}
export default PaginatedFlatList;
typescript扩展版本
import React, { useState, useEffect } from 'react';
import { FlatList, RefreshControl, ActivityIndicator, Text, View } from 'react-native';
interface IPaginatedFlatListProps<T> {
renderItem: (item: T, index: number) => React.ReactNode;
keyExtractor: (item: T, index: number) => string;
data: T[];
onFetchData: (page: number) => Promise<T[]>;
initialPage?: number;
pageSize?: number;
refreshControlProps?: RefreshControlProps;
errorComponent?: React.ReactNode;
noDataComponent?: React.ReactNode;
}
function PaginatedFlatList<T>({
renderItem,
keyExtractor,
data,
onFetchData,
initialPage = 1,
pageSize = 10,
refreshControlProps,
errorComponent = <Text>Oops! Something went wrong.</Text>,
noDataComponent = <Text>No data found.</Text>,
}: IPaginatedFlatListProps<T>) {
// 初始化 state
const [page, setPage] = useState(initialPage);
const [isLoading, setIsLoading] = useState(false);
const [isRefreshing, setIsRefreshing] = useState(false);
const [hasMoreData, setHasMoreData] = useState(true);
const [error, setError] = useState<Error | null>(null);
// 当 page 发生变化时,重新获取数据
useEffect(() => {
fetchData();
}, [page]);
// 获取数据
const fetchData = async () => {
// 判断是否需要分页获取
if (!hasMoreData || isLoading) {
return;
}
setIsLoading(true);
setError(null);
try {
const newData = await onFetchData(page);
if (newData.length < pageSize) {
// 如果返回的数据少于 pageSize,说明没有更多数据了
setHasMoreData(false);
}
// 将新数据添加到现有数据后面
setData([...data, ...newData]);
} catch (error) {
setError(error);
}
setIsLoading(false);
};
// 刷新数据
const refreshData = async () => {
if (isRefreshing) {
return;
}
setIsRefreshing(true);
setPage(initialPage);
setHasMoreData(true);
setError(null);
try {
const newData = await onFetchData(initialPage);
setData(newData);
} catch (error) {
setError(error);
}
setIsRefreshing(false);
};
// 渲染每个 item
const renderItemWithIndex = ({ item, index }: { item: T; index: number }) => renderItem(item, index);
// 渲染 FlatList
return (
<FlatList
data={data}
renderItem={renderItemWithIndex}
keyExtractor={keyExtractor}
refreshControl={
<RefreshControl refreshing={isRefreshing} onRefresh={refreshData} {...refreshControlProps} />
}
onEndReachedThreshold={0.1}
onEndReached={fetchData}
ListFooterComponent={
isLoading ? (
<ActivityIndicator size="large" />
) : (
!hasMoreData && (
<View>
{!data.length ? noDataComponent : null}
{error ? errorComponent : null}
</View>
)
)
}
/>