实现分页加载Select


需求来源,指派工单的时候选择指定的人员,但是人员数据比较庞大,请求会花费不少时间,所以我们选择了用根据分页来请求数据,来做到快速请求到数据。

最后实现的效果如下:

Ant Design的文档中并没有明确地给出直接可以用的事件,但是我们可以通过它给的2个参数来完成这个需求

参数 说明
dropdownRender 自定义下拉框内容
onPopupScroll 下拉列表滚动时的回调
  • dropdownRender: 来负责渲染的逻辑,包括loading动画
  • onPopupScroll: 来负责当滚动到底部的时候,根据当前页去判断是否继续请求

简单画个脑图,来看下我们需要考虑哪些东西

HTML

1
2
3
4
5
6
7
8
9
10
11
12
<Select
style={{ width: 220 }}
onPopupScroll={onPopupScroll}
onSelect={onSelect}
dropdownRender={(menu) => dropdownRender(menu)}
>
{
list?.length > 0 && list.map((item, index) => {
return <Option key={item[OptionsConfig.optionValue]} value={item[OptionsConfig.optionValue]}>{item[OptionsConfig.optionName]}-{index}</Option>
})
}
</Select>
1
2
3
4
5
6
7
const dropdownRender = useCallback((menu) => {
return (
<Spin spinning={isLoading}>
{menu}
</Spin>
)
}, [isLoading])

主要看dropdownRender,我们可以看到传入了一个menumenu就是children Node,包了一层Spin来控制加载动画,这就完成了基本的HTML

js

1
2
3
4
5
6
7
8
const onPopupScroll = useCallback(async(e) => {
const { target } = e;
if (target.scrollTop + target.offsetHeight === target.scrollHeight) {
const { current } = pagination;
setPagination({ current: current + 1, size: 10 });
handleGetData();
}
}, [handleGetData, pagination])

这块主要就是判断是否滚动到底部的代码

我们先分别来看下这三个属性

参数 说明
scrollTop 返回当前视图中的实际元素的顶部边缘和顶部边缘之间的距离
offsetHeight 返回元素的高度包括边框和内边距,但不包括外边距
scrollHeight 返回整个元素的高度(包括带滚动条的隐蔽的地方)

画的太抽象了。。。

所以当element.offsetHeight + element.scrollTop === element.scrollHeight就得出已经滚动到底部了。

完整代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
import React, { useState, useEffect, useMemo, useCallback } from 'react';
import { Select, Spin } from 'antd';

interface OptionsConfigType {
optionValue: string;
optionName: string;
}

interface IProps {
url: string;
OptionsConfig: OptionsConfigType;
handleSelect: (value, options?) => void;
}

const { Option } = Select;
export const SuperSelect: React.FC<IProps> = (props) => {
const { url, OptionsConfig, handleSelect } = props;
const [pagination, setPagination] = useState<any>({ current: 1, size: 10 }); // 默认分页
const [isLoading, setIsLoading] = useState(false); // loading动画控制
const [total, setTotal] = useState<number>(0); // 记录总数,来判断是否继续请求
const [list, setList] = useState<any[]>([]);

const handleGetData = useCallback(() => {
if (list.length && list.length >= total) return; // 判断是否继续请求
setIsLoading(true);
fetch(url) // 模拟请求
.then(res => res.json())
.then((json) => {
setList([...list, ...json.records]);
setTotal(json.total);
setIsLoading(false);
})
}, [list, total])

useEffect(() => {
handleGetData();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [])

const onSelect = useCallback((value, options) => {
handleSelect && handleSelect(value, options);
}, [handleSelect])

const dropdownRender = useCallback((menu) => {
return (
<Spin spinning={isLoading}>
{menu}
</Spin>
)
}, [isLoading])

const onPopupScroll = useCallback(async(e) => {
const { target } = e;
if (target.scrollTop + target.offsetHeight === target.scrollHeight) {
const { current } = pagination;
setPagination({ current: current + 1, size: 10 });
handleGetData();
}
}, [handleGetData, pagination])

return useMemo(() => {
return (
<>
<Select
style={{ width: 220 }}
onPopupScroll={onPopupScroll}
onSelect={onSelect}
dropdownRender={(menu) => dropdownRender(menu)}
>
{
list?.length > 0 && list.map((item, index) => {
return <Option key={item[OptionsConfig.optionValue]} value={item[OptionsConfig.optionValue]}>{item[OptionsConfig.optionName]}-{index}</Option>
})
}
</Select>
</>
)
}, [OptionsConfig.optionName, OptionsConfig.optionValue, dropdownRender, list, onPopupScroll, onSelect])
}

export default SuperSelect;

我的微信公众号: 梨的前端小屋


文章作者: 梨啊梨
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 梨啊梨 !
  目录