文章目录
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\].