React Native必备基础建设之从0封装FlatList组件

326 阅读1分钟

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>
          )
        )
      }
   />