阅读 531

如何封装一个antd Select 组件下拉框分页加载数据的组件| 8月更文挑战

1.背景介绍

antd作为目前react最常用的组件库,他本身为我们提供了,Select的选择器,但是他的选择器并没有提供搜素时下拉加载更多的功能。因此由于项目的需要,所以封装了一个。

2. github上的参考问题

  1. 在github的提问上就有类似的问题:【# select 下拉框的滚动回调onPopupScroll的参数中增加:当前滚动是否已经滚动到底部的bool值】github.com/ant-design/…

在回答中,有人提到,可以使用e.target.scrollHeight和scrollTop去做。来判断是否到达底部。

  1. 另外也有人提到到【# I wish Select component to provide onScrollToEnd api (希望 Select 组件提供 onScrollToEnd API)】github.com/ant-design/…

在这个问题的回答底部,有提供到解决方案。 select组件下,onPopupScroll的API这样子可以自定义模拟 scrollToEnd。在组件中选onPopupScroll,并且监听滚动的位置,可以达到效果。

3.其他问题

1.改变时重复加载了两次

其实在加载更多的里面,需要考虑的问题除了加载分页,以外,还有考虑就是当搜素框的内容变化时,需要重新搜素,并且都从第一页搜素。一开始,我选择了将text,pageNum,都分别作为一个state,来进行保存,但当我重新搜素时,两者都需要重新set,那触发useEffect就会触发两次,因此我就加入了ahooks中的useSetState,可以将两者保存在一个对象中,那两者都改变的时候,就也只会触发一次。

2. filterOption的重要性

  1. 一开始我没有加上filterOption为false,antd中filterOption是默认为true,开启的,因此当我一开始已经有了数据,再进行搜素的时候,他都会在因为filterOption直接过滤掉了,所以需要加上这个为false,数据都是由后端传入,我们进行维护。

3.模拟request

模拟request的话,其实比较简单,就通过promise来实现,通过一个setttimeout的延时,然后过一段时间返回resolve就行。

4. 减少搜素的次数

通过debounce的使用,减少这个次数。

4. 代码封装

注意点:

  1. 需要手动封装requestList,这块逻辑,主要是将处理完的数据,返回给useEffect中的函数,进行处理,然后拼接到data中
  2. 需要手动去维护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;

复制代码
文章分类
前端
文章标签