0803分页_加载更多-网络ajax请求2-react-仿低代码平台项目

文章目录

    • [1 分页](#1 分页)
      • [1.1 url与分页参数](#1.1 url与分页参数)
      • [1.2 分页组件与url](#1.2 分页组件与url)
      • [1.3 列表页引用分页组件](#1.3 列表页引用分页组件)
    • [2 加载更多](#2 加载更多)
      • [2.1 状态](#2.1 状态)
      • [2.2 触发时机](#2.2 触发时机)
      • [2.3 加载数据](#2.3 加载数据)
      • 2.4优化
    • 结语

1 分页

1.1 url与分页参数

查询问卷列表接口,添加分页参数:

  • page:当前页码(第几页)
  • pageSize:每页多少条记录

question.ts 查询文件接口列表参数扩展,如下所示:

ts 复制代码
type SearchOption = {
  keyword: string;
  isStar: boolean;
  isDeleted: boolean;
  page: number;
  pageSize: number;
};

浏览器url获取分页参数,useLoadQuestionListData.ts代码如下所示:

ts 复制代码
import { useSearchParams } from "react-router-dom";
import { useRequest } from "ahooks";
import { getQuestionListApi } from "@/api/question";

import {
  LIST_SEARCH_PARAM_KEY,
  LIST_PAGE_PARAM_KEY,
  LIST_PAGE_SIZE_PARAM_KEY,
  LIST_PAGE_SIZE_DEFAULT,
  LIST_PAGE_DEFAULT,
} from "@/constant";

type OptionType = {
  isStar: boolean;
  isDeleted: boolean;
  page: number;
  pageSize: number;
};

/**
 * 获取问卷列表
 * @returns 问卷列表
 */
function useLoadQuestionListData(opt: Partial<OptionType>) {
  const { isStar, isDeleted } = opt;
  const [searchParams] = useSearchParams();
  const keyword = searchParams.get(LIST_SEARCH_PARAM_KEY) || "";
  const page =
    parseInt(searchParams.get(LIST_PAGE_PARAM_KEY) || "") || LIST_PAGE_DEFAULT ;
  const pageSize =
    parseInt(searchParams.get(LIST_PAGE_SIZE_PARAM_KEY) || "") ||
    LIST_PAGE_SIZE_DEFAULT;

  async function load() {
    const data = await getQuestionListApi({
      keyword,
      isStar,
      isDeleted,
      page,
      pageSize,
    });
    //...
}

export default useLoadQuestionListData;

服务端解析分页参数,question.js代码如下:

js 复制代码
const Mock = require('mockjs')

const getQuestionList = require("./data/getQuestionList")

const Random = Mock.Random

module.exports = [
  // ...
  {
    // 获取问卷列表
    url: '/api/question',
    method: 'get',
    response(ctx) {
      const { query = {} } = ctx
      
      const isStar = query.isStar === 'true'
      const isDeleted = query.isDeleted === 'true'
      const page = parseInt(query.page) || 1
      const pageSize = parseInt(query.pageSize) || 10
      return {
        errno: 0,
        data: {
          list: getQuestionList({isStar, isDeleted, page, pageSize}),
          total: 100
        }
      }
    }
  },
]

1.2 分页组件与url

antd分页组件与url交互

由于多个列表页面都需要分页组件,这里我们以antd分页组件为基础,封装自定义分页组件,并且实现分页参数与url参数交互,ListPage.tsx代码如下所示:

tsx 复制代码
import { FC, useEffect, useState } from "react";
import { useLocation, useNavigate, useSearchParams } from "react-router-dom";
import { Pagination } from "antd";

import {
  LIST_PAGE_DEFAULT,
  LIST_PAGE_PARAM_KEY,
  LIST_PAGE_SIZE_DEFAULT,
  LIST_PAGE_SIZE_PARAM_KEY,
} from "@/constant";

type PropsType = {
  total: number;
};

const ListPage: FC<PropsType> = (props: PropsType) => {
  const [current, setCurrent] = useState(LIST_PAGE_DEFAULT);
  const [pageSize, setPageSize] = useState(LIST_PAGE_SIZE_DEFAULT);

  const [searchParams] = useSearchParams();

  useEffect(() => {
    const curPage =
      parseInt(searchParams.get(LIST_PAGE_PARAM_KEY) || "") ||
      LIST_PAGE_DEFAULT;
    const curPageSize =
      parseInt(searchParams.get(LIST_PAGE_SIZE_PARAM_KEY) || "") ||
      LIST_PAGE_SIZE_DEFAULT;

    setCurrent(curPage);
    setPageSize(curPageSize);
  }, [searchParams]);

  const { total } = props;

  // 当page pageSize改变时,跳转页面(改变url)
  const nav = useNavigate();
  const { pathname } = useLocation();

  function handlePageChange(page: number, pageSize: number) {
    searchParams.set(LIST_PAGE_PARAM_KEY, page.toString());
    searchParams.set(LIST_PAGE_SIZE_PARAM_KEY, pageSize.toString());
    nav({
      pathname,
      search: searchParams.toString(),
    });
  }

  return (
    <Pagination
      current={current}
      pageSize={pageSize}
      total={total}
      onChange={handlePageChange}
    />
  );
};

export default ListPage;

1.3 列表页引用分页组件

"星标问卷"列表页Star.tsx代码如下:

tsx 复制代码
// ...
import ListPage from "@/components/ListPage";

const List: FC = () => {
  // ...
  //问卷列表数据
  const { data = {}, loading } = useLoadQuestionListData({ isStar: true });
  const { list = [], total = 0 } = data;
// ...
      <div className={styles.footer}>
        <ListPage total={total} />
      </div>
    </>
  );
};

export default List;

"回收站"列表页Trash.tsx代码如下所示:

tsx 复制代码
import { FC, useState } from "react";
import { useTitle } from "ahooks";
import ListPage from "../../components/ListPage";


const List: FC = () => {
  useTitle("调查问卷-回收站");

  //问卷列表数据
  const { data = {}, loading } = useLoadQuestionListData({ isDeleted: true });
  const { list = [], total = 0 } = data;
// ...
      <div className={styles.footer}>
        <ListPage total={total} />
      </div>
    </>
  );
};

export default List;

2 加载更多

2.1 状态

基础

  • page:当前页
  • list:全部数据列表,上划累加
  • total:总条数

计算项

  • hasMoreData:是否有更多数据

2.2 触发时机

  • 页面加载触发
  • 页面滚动到加载更多数据时触发
    • 监听页面滚动
    • 防抖处理
    • DOM计算页面滚动刀"加载更多"

2.3 加载数据

  • useRequest请求接口
  • 接口返回数据,设置状态

2.4优化

  • 加载更多,缓存处理
  • 刷新页面优化暂无数据
  • 搜索重置状态

完整"我的问卷"页List.tsx代码如下所示:

tsx 复制代码
import { FC, useEffect, useMemo, useRef, useState } from "react";
import { useSearchParams } from "react-router-dom";
import { useDebounceFn, useRequest, useTitle } from "ahooks";
import { Typography, Spin, Empty } from "antd";

import QuestionCard from "@/components/QuestionCard";
import ListSearch from "@/components/ListSearch";
import styles from "./common.module.scss";
import { getQuestionListApi } from "@/api/question";
import { LIST_PAGE_SIZE_DEFAULT, LIST_SEARCH_PARAM_KEY } from "@/constant";

const { Title } = Typography;

const List: FC = () => {
  useTitle("调查问卷-我的问卷");

  //问卷列表数据
  const [started, setStarted] = useState(false);
  const [page, setPage] = useState(1);
  const [list, setList] = useState([]); // 全部列表数据,上划加载更多,累计
  const [total, setTotal] = useState(0);

  const hasMoreData = total > list.length;

  const [searchParams] = useSearchParams();
  const keyword = searchParams.get(LIST_SEARCH_PARAM_KEY) || "";

  // 搜索重置状态
  useEffect(() => {
    setStarted(false);
    setPage(1);
    setList([]);
    setTotal(0);
  }, [keyword]);

  // 加载数据
  const { run: loadData, loading } = useRequest(
    async () => {
      const data = await getQuestionListApi({
        page,
        pageSize: LIST_PAGE_SIZE_DEFAULT,
        keyword,
      });
      return data;
    },
    {
      manual: true,
      onSuccess(res) {
        const { list: newList = [], total = 0 } = res;
        // 累计数据
        setList(list.concat(newList));
        setTotal(total);
        setPage(page + 1);
      },
    }
  );
  // 尝试触发加载-防抖处理
  const containerRef = useRef<HTMLDivElement>(null);
  const { run: tryLoadMore } = useDebounceFn(
    () => {
      const elem = containerRef.current;
      if (elem == null) {
        return;
      }
      // 判断如果div bottom 小于等于页面的高度
      const domRect = elem.getBoundingClientRect();
      if (domRect == null) {
        return;
      }
      const { bottom } = domRect;
      if (bottom <= document.body.clientHeight) {
        // 加载数据
        loadData();
        setStarted(true);
      }
    },
    { wait: 500 }
  );

  // 触发时机:页面加载或者url参数(keyword)变化时
  useEffect(() => {
    // 第一次加载,初始化
    tryLoadMore();
  }, [searchParams]);

  // 监听页面滚动事件
  useEffect(() => {
    if (hasMoreData) {
      window.addEventListener("scroll", tryLoadMore);
    }

    return () => {
      // 解绑事件
      window.removeEventListener("scroll", tryLoadMore);
    };
  }, [searchParams, hasMoreData]);

  // 加载更多显示优化
  const LoadMoreContentElem = useMemo(() => {
    if (!started || loading) {
      return <Spin />;
    }
    if (total === 0) {
      return <Empty description="暂无数据" />;
    }
    if (!hasMoreData) {
      return <span>没有更多了......</span>;
    }
    return <span>开始加载下一页</span>;
  }, [started, loading, hasMoreData]);
  return (
    <>
      <div className={styles.header}>
        <div className={styles.left}>
          <Title level={3}>我的问卷</Title>
        </div>
        <div className={styles.right}>
          <ListSearch />
        </div>
      </div>
      <div className={styles.content}>
        {list.length > 0 &&
          list.map((q: any) => {
            const { _id } = q;
            return <QuestionCard key={_id} {...q} />;
          })}
      </div>
      <div className={styles.footer}>
        <div ref={containerRef}>{LoadMoreContentElem}</div>
      </div>
    </>
  );
};

export default List;

结语

❓QQ:806797785

⭐️仓库地址:https://gitee.com/gaogzhen

⭐️仓库地址:https://github.com/gaogzhen

1\][ahook官网](https://ahooks.js.org/)\[CP/OL\]. \[2\][mock文档](https://github.com/nuysoft/Mock/wiki/Getting-Started)\[CP/OL\]. \[3\][Ant Design官网](https://ant-design.antgroup.com/index-cn)\[CP/OL\].

相关推荐
霸王蟹3 小时前
React Fiber 架构深度解析:时间切片与性能优化的核心引擎
前端·笔记·react.js·性能优化·架构·前端框架
yyywoaini~3 小时前
wordcount程序
前端·javascript·ajax
源码方舟9 小时前
【HTML5】【AJAX的几种封装方法详解】
ajax·okhttp·html5
outstanding木槿9 小时前
react中安装依赖时的问题 【集合】
前端·javascript·react.js·node.js
敖云岚9 小时前
【前端三剑客】Ajax技术实现前端开发
ajax·okhttp
霸王蟹10 小时前
React中useState中更新是同步的还是异步的?
前端·javascript·笔记·学习·react.js·前端框架
霸王蟹10 小时前
React Hooks 必须在组件最顶层调用的原因解析
前端·javascript·笔记·学习·react.js
Coding的叶子14 小时前
React Flow 节点事件处理实战:鼠标 / 键盘事件全解析(含节点交互代码示例)
react.js·交互·鼠标事件·fgai·react agent
it_remember20 小时前
新建一个reactnative 0.72.0的项目
javascript·react native·react.js
ZHOU_WUYI1 天前
使用 Docker 部署 React + Nginx 应用教程
nginx·react.js·docker