import React, { useState, useEffect } from 'react';
interface Option {
id: number;
value: string;
}
interface Props {
options: Option[];
onSelect: (option: Option) => void;
loadMore: (startIndex: number) => Promise<Option[]>;
}
const Dropdown: React.FC<Props> = ({ options, onSelect, loadMore }) => {
const [searchTerm, setSearchTerm] = useState<string>('');
const [filteredOptions, setFilteredOptions] = useState<Option[]>(options);
const [showDropdown, setShowDropdown] = useState<boolean>(false);
const [loadingMore, setLoadingMore] = useState<boolean>(false);
const [startIndex, setStartIndex] = useState<number>(options.length);
const [endIndex, setEndIndex] = useState<number>(
Math.min(options.length + 10, options.length)
);
const [lastScrollPosition, setLastScrollPosition] = useState<number>(0);
useEffect(() => {
setFilteredOptions(
options.filter(option =>
option.value.toLowerCase().includes(searchTerm.toLowerCase())
)
);
setEndIndex(Math.min(options.length + 10, options.length));
}, [options, searchTerm]);
const handleSearch = (e: React.ChangeEvent<HTMLInputElement>) => {
setSearchTerm(e.target.value);
if (!showDropdown) {
setShowDropdown(true);
}
};
const handleSelect = (option: Option) => {
onSelect(option);
setShowDropdown(false);
};
const handleScroll = (e: React.UIEvent<HTMLDivElement>) => {
const { scrollHeight, scrollTop, clientHeight } = e.currentTarget;
const scrollDelta = scrollTop - lastScrollPosition;
setLastScrollPosition(scrollTop);
if (
scrollDelta > 0 &&
scrollTop + clientHeight >= scrollHeight &&
!loadingMore
) {
setLoadingMore(true);
loadMore(startIndex)
.then(newOptions => {
const nextStartIndex = endIndex;
const nextEndIndex = Math.min(
endIndex + 10,
options.length + newOptions.length
);
setStartIndex(nextStartIndex);
setEndIndex(nextEndIndex);
setFilteredOptions(prev => [...prev, ...newOptions]);
setLoadingMore(false);
})
.catch(() => setLoadingMore(false));
}
};
return (
<div className="dropdown" onScroll={handleScroll}>
<input
type="text"
placeholder="Search..."
value={searchTerm}
onChange={handleSearch}
/>
{showDropdown && (
<div className="dropdown-menu">
{filteredOptions.slice(0, endIndex).map(option => (
<div key={option.id} onClick={() => handleSelect(option)}>
{option.value}
</div>
))}
{loadingMore && <div className="loading">Loading...</div>}
</div>
)}
</div>
);
};
export default Dropdown;
该下拉框组件接受一个 options 数组、一个 onSelect 回调函数和一个 loadMore 回调函数作为props。其中, options 包含所有选项, onSelect 会在选择一个选项后被调用, loadMore 会在滚动到下拉框底部时被调用以加载更多选项。 该组件使用 useState 和 useEffect 来维护搜索词、筛选后的选项、下拉框的显示状态、是否正在加载更多选项、滚动条位置等各种状态。通过 handleSearch 和 handleSelect 函数来处理搜索词的输入和选项的选择。通过 handleScroll 函数来监听下拉框滚动事件,当滚动到底部时会触发 loadMore 回调函数来加载更多选项。 下拉框的滚动懒加载是通过 startIndex 和 endIndex 这两个状态来实现的。 startIndex 表示当前已显示的选项的最后一个索引值, endIndex 表示当前要显示的选项的索引范围。当滚动到底部时,会根据 endIndex 来计算下一次加载选项时的起始和结束索引。使用 slice 方法来截取当前要显示的选项。使用 loadingMore 状态来控制加载更多的提示信息的显示与隐藏。 该下拉框组件使用TypeScript编写,包括了对组件的props和状态的类型声明,提高了代码可读性和可维护性。