1.背景介绍
antd作为目前react最常用的组件库,他本身为我们提供了,Select的选择器,但是他的选择器并没有提供搜素时下拉加载更多的功能。因此由于项目的需要,所以封装了一个。
2. github上的参考问题
- 在github的提问上就有类似的问题:【# select 下拉框的滚动回调onPopupScroll的参数中增加:当前滚动是否已经滚动到底部的bool值】github.com/ant-design/…
在回答中,有人提到,可以使用e.target.scrollHeight和scrollTop去做。来判断是否到达底部。
- 另外也有人提到到【# I wish
Selectcomponent to provideonScrollToEndapi (希望 Select 组件提供 onScrollToEnd API)】github.com/ant-design/…
在这个问题的回答底部,有提供到解决方案。 select组件下,onPopupScroll的API这样子可以自定义模拟 scrollToEnd。在组件中选onPopupScroll,并且监听滚动的位置,可以达到效果。
3.其他问题
1.改变时重复加载了两次
其实在加载更多的里面,需要考虑的问题除了加载分页,以外,还有考虑就是当搜素框的内容变化时,需要重新搜素,并且都从第一页搜素。一开始,我选择了将text,pageNum,都分别作为一个state,来进行保存,但当我重新搜素时,两者都需要重新set,那触发useEffect就会触发两次,因此我就加入了ahooks中的useSetState,可以将两者保存在一个对象中,那两者都改变的时候,就也只会触发一次。
2. filterOption的重要性
- 一开始我没有加上filterOption为false,antd中filterOption是默认为true,开启的,因此当我一开始已经有了数据,再进行搜素的时候,他都会在因为filterOption直接过滤掉了,所以需要加上这个为false,数据都是由后端传入,我们进行维护。
3.模拟request
模拟request的话,其实比较简单,就通过promise来实现,通过一个setttimeout的延时,然后过一段时间返回resolve就行。
4. 减少搜素的次数
通过debounce的使用,减少这个次数。
4. 代码封装
注意点:
- 需要手动封装requestList,这块逻辑,主要是将处理完的数据,返回给useEffect中的函数,进行处理,然后拼接到data中
- 需要手动去维护total这个状态,就是总共有多少,超过total就不需要继续加载了
import React, { useState, useEffect } from 'react';
import { Select, Spin } from 'antd';
import { debounce } from 'lodash';
import { useSetState } from 'ahooks';
const { Option } = Select;
const SelectSearch = ({value = [], onChange = () => {}}) => {
const [total, setTotal] = useState(30);
const [data, setData] = useState([]);
const [loading, setLoading] = useState(false);
const [pageText, setPageText] = useSetState({
pageNum: 1,
text: '',
});
const timeout = (time, data) =>
new Promise((resolve) => {
setTimeout(() => resolve(data), time);
});
const requestList = () => {
return timeout(1000, [...Array(10)].map((item, index) => `${index}+${pageText?.text || ''}`))
.then((res) => {
return Promise.resolve(res || []);
})
.catch(() => {
// eslint-disable-next-line prefer-promise-reject-errors
return Promise.reject([]);
});
};
const handleScroll = (e) => {
e.persist();
// 判断滑动到底部
const { scrollTop, scrollHeight, clientHeight } = e.target;
return scrollHeight - scrollTop === clientHeight;
};
useEffect(() => {
setLoading(true);
requestList()
.then((res) => {
const resArr = Array.isArray(res) ? res : [];
setData([...data, ...resArr]);
})
.finally(() => setLoading(false));
}, [pageText]);
const handlePopupScroll = (e) => {
// 加载
if (handleScroll(e) && data.length < total) {
setPageText((prev) => ({
pageNum: prev.pageNum + 1,
}));
}
};
const handleChangeText = (v) => {
setTotal(0);
setData([]);
setPageText({
text: v,
pageNum: 1,
});
};
const onSearch = debounce(
handleChangeText,
800,
);
const renderOptions = data?.map((item) => {
return (
<Option value={item} key={item} label={item}>
{item}
</Option>
);
});
return (
<div>
<Select
showSearch
style={{ width: 200 }}
onSearch={onSearch}
notFoundContent={loading ? <Spin size="small" /> : '无'}
placeholder="请选择"
onPopupScroll={(e) => {
handlePopupScroll(e);
}}
filterOption={false}
value={value}
onSelect={onChange}
>
{renderOptions}
</Select>
</div>
);
};
export default SelectSearch;