用react和ts实现带模糊搜索和滚动懒加载的下拉框组件

313 阅读2分钟
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 会在滚动到下拉框底部时被调用以加载更多选项。 该组件使用 useStateuseEffect 来维护搜索词、筛选后的选项、下拉框的显示状态、是否正在加载更多选项、滚动条位置等各种状态。通过 handleSearchhandleSelect 函数来处理搜索词的输入和选项的选择。通过 handleScroll 函数来监听下拉框滚动事件,当滚动到底部时会触发 loadMore 回调函数来加载更多选项。 下拉框的滚动懒加载是通过 startIndexendIndex 这两个状态来实现的。 startIndex 表示当前已显示的选项的最后一个索引值, endIndex 表示当前要显示的选项的索引范围。当滚动到底部时,会根据 endIndex 来计算下一次加载选项时的起始和结束索引。使用 slice 方法来截取当前要显示的选项。使用 loadingMore 状态来控制加载更多的提示信息的显示与隐藏。 该下拉框组件使用TypeScript编写,包括了对组件的props和状态的类型声明,提高了代码可读性和可维护性。