React 列表定时轮询刷新+滚动加载

image

1、安装依赖

npm i antd
npm i react-infinite-scroll-component

2、DataList.tsx

import React, { useEffect, useRef } from "react";
import { Spin } from "antd";
import InfiniteScroll from "react-infinite-scroll-component";
import { useListStore } from "./store/list";
import { getDataList, refreshDataList } from "./store/list/action";

export default function DataList() {
  const { dataList, total, getListLoading, hasMore, refreshLoading } =
    useListStore();

  const clearDataListTimer = useRef<() => void>(() => { });

  const init = async () => {
    await getDataList();

    const dataListTimer = await refreshDataList();
    clearDataListTimer.current = () => {
      clearInterval(dataListTimer);
    };
  };

  useEffect(() => {
    (async () => {
      await init();
    })();
    return () => {
      clearDataListTimer.current();
    };
  }, []);

  return (
    <div>
      <h2>Data List total: {total}</h2>
      {refreshLoading && <p>Refreshing...</p>}
      {hasMore && !getListLoading && (
        <button onClick={getDataList}>Click:Load More</button>
      )}
      <div id='list' style={{ height: "400px", overflowY: "auto" }}>
        <Spin spinning={getListLoading}>
          <InfiniteScroll
            dataLength={dataList.length}
            next={getDataList}
            hasMore={!!hasMore}
            loader={null}
            scrollableTarget={"list"}
          >
            {dataList.map((item) => (
              <div key={item.id}>
                <h3>{item.name}</h3>
                <p>{item.description}</p>
              </div>
            ))}
          </InfiniteScroll>
          {!dataList.length && <p>No data</p>}
        </Spin>
      </div>
    </div>
  );
}

2、store\list
-- index.ts

import { create } from 'zustand';

export interface IListState {
  readonly dataList: ReadonlyArray<any>;
  readonly page: {
    pageNum: number; // 页码
    pageSize: number; // 每页数量
  };
  readonly total: number;
  readonly getListLoading: boolean;
  readonly hasMore: boolean;
  readonly refreshLoading: boolean;
}

const initialState: IListState = {
  dataList: [],
  page: {
    pageNum: 0,
    pageSize: 10,
  },
  total: 0,
  getListLoading: false,
  hasMore: true,
  refreshLoading: false,
};

export const useListStore = create<IListState>(
  () => initialState,
);

3、store\list
-- action.ts

import { useListStore } from ".";
import { getList } from "../../api";
import { loopHandler } from "../../utils";

const { setState, getState } = useListStore;

/**
 * 获取dataList
 */
export const getDataList = async () => {
    try {
        const { dataList, page } = getState();
        const newPage = { ...page, pageNum: page.pageNum + 1 };

        setState({ getListLoading: true });
        const { list, total_size } = await getList(
            newPage.pageNum,
            newPage.pageSize,
        );
        if (Array.isArray(list)) {
            const newDataList = [...dataList, ...list];
            const hasMore =
                total_size > newDataList.length;

            setState({
                dataList: newDataList,
                total: total_size,
                hasMore,
                page: { ...newPage },
            });
        }

    } catch (error) {
        console.error('getDataList error.', error);

    }
    setState({ getListLoading: false });
};

/**
 * 刷新所有dataList
 */
const loopRefreshDataList = async () => {
    const { page } = getState();
    const curPage = page.pageNum || 1;
    const dataList = [];
    let newTotal = 0;
    let lastPage = curPage;

    setState({ refreshLoading: true });
    try {
        for (let i = 1; i <= curPage; i++) {
            const { list, total_size } =
                await getList(i, page.pageSize);
            if (!list) {
                break;
            }
            newTotal = total_size;
            dataList.push(...list);
            if (
                list.length <= page.pageSize &&
                dataList.length === total_size
            ) {
                lastPage = i;
                break;
            }
        }
        const hasMore = newTotal > dataList.length;
        setState({
            dataList,
            hasMore,
            page: { ...page, pageNum: lastPage },
            total: newTotal,
        });
    } catch (error) {
        console.error('loopRefreshDataList error.', error);
    }
    setState({ refreshLoading: false });
};

// 每三十秒刷新dataList
export const refreshDataList = async () => {
    const loopInterval = 30 * 1000;
    return await loopHandler(loopRefreshDataList, loopInterval)();
};

4、utils.ts

export const loopHandler = (
    fn: (...args: any) => Promise<any>,
    delay: number,
) => {
    let timer: any = null;
    return () => {
        if (timer) {
            clearInterval(timer);
        }
        timer = setInterval(async () => {
            await fn();
        }, delay);
        return timer;
    };
};

export const sleep = async (delay: number) =>
    await new Promise((resolve) => setTimeout(resolve, delay));

5、api.ts

import { sleep } from "./utils";

interface PaginatedResponse<T> {
    page: number;
    page_size: number;
    total_size: number;
    list: T[];
}

export const getList = async (
    page: number,
    pageSize: number,
): Promise<PaginatedResponse<any>> => {
    //   return await request<PaginatedResponse<any>>(
    //     `/get_list`,
    //     { page: page, page_size: pageSize },
    //   );
    // 模拟虚拟数据
    await sleep(500)
    const list = Array.from({ length: pageSize }, (_, index) => ({
        id: `item-${(page - 1) * pageSize + index + 1}`,
        name: `Item ${(page - 1) * pageSize + index + 1}`,
        description: `This is item ${(page - 1) * pageSize + index + 1}`,
    }));

    const total_size = 100; // 模拟总数据量为 100

    return {
        page,
        page_size: pageSize,
        total_size,
        list,
    };
}
posted @ 2025-08-25 16:55  苏沐~  阅读(14)  评论(0)    收藏  举报