likes
comments
collection
share

React组件封装(二): 懒加载下拉框 LazyLoadSelect

作者站长头像
站长
· 阅读数 60

需求

根据项目需求,需要封装一个可以懒加载的 Select 组件,他具有以下的功能:

  • 下拉框滚动到底部时,加载下一页的数据,可以自定义 pageSize。当接口返回的条数小于 pageSize 时,取消加载
  • 支持自定义渲染 options
  • 父组件支持修改子组件的状态(ref + useImperativeHandle)
  • 继承 Select 全部属性

封装

首先定义默认状态,如默认的请求参数 { current: 1, pageSize: 20 }extraParamsFromOutSide(父组件支持修改内部组件的状态)dataList(数据列表)是否还有更多数据的判断

整体架子

import { useBoolean, useDebounceFn, useRequest, useSetState } from 'ahooks';
import { Select, Spin } from 'antd';
import React, { useImperativeHandle, useState } from 'react';

const { Option } = Select;

interface Res {
  data: any;
  success: boolean;
  code: number;
}

interface PagetionProps {
  currentPage: number;
  pageSize: number;
}

interface ExtraParams {
  [key: string]: any;
}

interface IRequestParams extends PagetionProps, ExtraParams {}

export interface IComponentProps {
  requestCallback: (params: IRequestParams) => Promise<any>; // 下拉框请求回调
  pageSize?: number; // 一次请求多少条数据
  emptyText?: string; // 数据请求到底后的展示文案
  distanceBottom?: number; // 下拉框滚动条距离底部多少距离就触发懒加载
  manual?: boolean; // 初始化下拉框的时候发请求
  customizeOptions?: any; // 自定义渲染下拉框选项的函数
  extraParams?: ExtraParams; // 额外的查询参数
}

export interface ILazySelectRef {
  setParams: (params: ExtraParams) => void;
}

const LazyLoadSelect = React.forwardRef<ILazySelectRef, IComponentProps>(
  (props, ref) => {
    const {
      requestCallback,
      pageSize = 20,
      emptyText = '没有更多了',
      distanceBottom = 0,
      manual = false,
      customizeOptions,
      extraParams,
    } = props;

    // 默认分页参数
    const [requestParams, setRequestParams] = useSetState<PagetionProps>({
      currentPage: 1,
      pageSize,
    });

    //外部组件可能额外改变的参数
    const [extraParamsFromOutSide, setExtraParamsFromOutSide] =
      useState<ExtraParams>({});

    // 是否还有更多数据
    const [
      isEmptyData,
      { setTrue: setEmptyDataTrue, setFalse: setEmptyDataFalse },
    ] = useBoolean(false);

    // 请求的数据列表
    const [dataList, setDataList] = useState<any>([]);

    // 用 useImperativeHandle 暴露一些外部ref能访问的属性
    useImperativeHandle(ref, () => ({
      setParams: (params: ExtraParams) => setExtraParamsFromOutSide(params),
    }));

    return (
      <Select
        showSearch
        loading={requestLoading}
        filterOption={false}
        onPopupScroll={(e) => onPopupScroll(e)}
        placeholder="请选择"
        onClear={handlerClear}
        {...props}
      >
       
      </Select>
    );
  },
);

架子搭好了,然后现在去实现数据的请求

数据请求

    // 请求
    const { loading: requestLoading, runAsync: runAsyncRequestList } =
      useRequest(
        async (params: PagetionProps) =>
          await requestCallback({
            ...extraParams,
            ...extraParamsFromOutSide,
            ...params,
          }),
        {
          manual,
          defaultParams: [{ ...requestParams, ...extraParamsFromOutSide }],
          onSuccess: (res: Res) => {
            if (res.code === 200) {
              // 获取列表数据
              const result = res?.data || [];
              setDataList([...dataList, ...result]);
              // 当返回条数小于分页条数,或者没有返回数据就代表没有更多数据
              if (result?.length < requestParams.pageSize) {
                setEmptyDataTrue();
              }
            }
          },
        },
      );

判断下拉框的滚动调是否滚动到 distanceBottom

// 判断是否滚动到底部
    const { run: onPopupScroll } = useDebounceFn(
      async (e) => {
        e.persist();
        const { scrollTop, offsetHeight, scrollHeight } = e.target;
        // 距离底部多少距离开始加载下一页
        if (
          scrollTop + offsetHeight + distanceBottom >= scrollHeight &&
          !isEmptyData
        ) {
          setRequestParams({ currentPage: requestParams.currentPage + 1 });
          await runAsyncRequestList({
            ...requestParams,
            currentPage: requestParams.currentPage + 1,
          });
        }
      },
      { wait: 350 },
    );

清空选择框的回调、重新请求的回调

// 重新请求
    const initRequest = async () => {
      // 搜索重置分页
      setRequestParams({ currentPage: 1 });
      setEmptyDataFalse();
      // 重置数据列表
      setDataList([]);
      await runAsyncRequestList({
        ...requestParams,
        ...extraParams,
        currentPage: 1,
      });
    };

    // 清除内容时的回调
    const handlerClear = async () => {
      // 当数据不足一页时,重新加载
      if (dataList.length < requestParams.pageSize) {
        initRequest();
      }
    };

自定义渲染 Options

<Select>
        {/* 渲染自定义 options */}
        {customizeOptions
          ? customizeOptions(dataList)
          : dataList?.map((item: any) => {
              return (
                <Option {...item} value={item.value} key={item.id}>
                  {item.label}
                </Option>
              );
            })}
</Select>

整体代码

/*
 * @Description: Select 下拉懒加载组件
 * @Version: 2.0
 * @Author: Cyan
 * @Date: 2023-03-10 10:04:17
 * @LastEditors: liuxiqin
 * @LastEditTime: 2023-08-22 16:07:10
 */
import { useBoolean, useDebounceFn, useRequest, useSetState } from 'ahooks';
import { Select, Spin } from 'antd';
import React, { useImperativeHandle, useState } from 'react';

const { Option } = Select;

interface Res {
  data: any;
  success: boolean;
  code: number;
}

interface PagetionProps {
  currentPage: number;
  pageSize: number;
}

interface ExtraParams {
  [key: string]: any;
}

interface IRequestParams extends PagetionProps, ExtraParams {}

export interface IComponentProps {
  requestCallback: (params: IRequestParams) => Promise<any>; // 下拉框请求回调
  pageSize?: number; // 一次请求多少条数据
  emptyText?: string; // 数据请求到底后的展示文案
  distanceBottom?: number; // 下拉框滚动条距离底部多少距离就触发懒加载
  manual?: boolean; // 初始化下拉框的时候发请求
  customizeOptions?: any; // 自定义渲染下拉框选项的函数
  extraParams?: ExtraParams; // 额外的查询参数
}

export interface ILazySelectRef {
  setParams: (params: ExtraParams) => void;
}

const LazyLoadSelect = React.forwardRef<ILazySelectRef, IComponentProps>(
  (props, ref) => {
    const {
      requestCallback,
      pageSize = 20,
      emptyText = '没有更多了',
      distanceBottom = 0,
      manual = false,
      customizeOptions,
      extraParams,
    } = props;

    // 默认分页参数
    const [requestParams, setRequestParams] = useSetState<PagetionProps>({
      currentPage: 1,
      pageSize,
    });

    //外部组件可能额外改变的参数
    const [extraParamsFromOutSide, setExtraParamsFromOutSide] =
      useState<ExtraParams>({});

    // 是否还有更多数据
    const [
      isEmptyData,
      { setTrue: setEmptyDataTrue, setFalse: setEmptyDataFalse },
    ] = useBoolean(false);

    // 请求的数据列表
    const [dataList, setDataList] = useState<any>([]);

    // 请求
    const { loading: requestLoading, runAsync: runAsyncRequestList } =
      useRequest(
        async (params: PagetionProps) =>
          await requestCallback({
            ...extraParams,
            ...extraParamsFromOutSide,
            ...params,
          }),
        {
          manual,
          defaultParams: [{ ...requestParams, ...extraParamsFromOutSide }],
          onSuccess: (res: Res) => {
            if (res.code === 200) {
              // 获取列表数据
              const result = res?.data || [];
              setDataList([...dataList, ...result]);
              // 当返回条数小于分页条数,或者没有返回数据就代表没有更多数据
              if (result?.length < requestParams.pageSize) {
                setEmptyDataTrue();
              }
            }
          },
        },
      );

    // 判断是否滚动到底部
    const { run: onPopupScroll } = useDebounceFn(
      async (e) => {
        e.persist();
        const { scrollTop, offsetHeight, scrollHeight } = e.target;
        // 距离底部多少距离开始加载下一页
        if (
          scrollTop + offsetHeight + distanceBottom >= scrollHeight &&
          !isEmptyData
        ) {
          setRequestParams({ currentPage: requestParams.currentPage + 1 });
          await runAsyncRequestList({
            ...requestParams,
            currentPage: requestParams.currentPage + 1,
          });
        }
      },
      { wait: 350 },
    );

    // 重新请求
    const initRequest = async () => {
      // 搜索重置分页
      setRequestParams({ currentPage: 1 });
      setEmptyDataFalse();
      // 重置数据列表
      setDataList([]);
      await runAsyncRequestList({
        ...requestParams,
        ...extraParams,
        currentPage: 1,
      });
    };

    // 清除内容时的回调
    const handlerClear = async () => {
      // 当数据不足一页时,重新加载
      if (dataList.length < requestParams.pageSize) {
        initRequest();
      }
    };

    // 用 useImperativeHandle 暴露一些外部ref能访问的属性
    useImperativeHandle(ref, () => ({
      setParams: (params: ExtraParams) => setExtraParamsFromOutSide(params),
    }));

    return (
      <Select
        showSearch
        loading={requestLoading}
        filterOption={false}
        onPopupScroll={(e) => onPopupScroll(e)}
        placeholder="请选择"
        onClear={handlerClear}
        {...props}
      >
        {/* 渲染自定义 options */}
        {customizeOptions
          ? customizeOptions(dataList)
          : dataList?.map((item: any) => {
              return (
                <Option {...item} value={item.value} key={item.id}>
                  {item.label}
                </Option>
              );
            })}
        {/* 没有更多数据 */}
        {isEmptyData && (
          <Option disabled>
            <div
              style={{
                color: '#bfbfbf',
                textAlign: 'center',
                fontSize: 12,
                pointerEvents: 'none',
              }}
            >
              {emptyText}
            </div>
          </Option>
        )}
        {/* 下拉加载按钮 */}
        {requestLoading && (
          <Option disabled>
            <div style={{ textAlign: 'center', pointerEvents: 'none' }}>
              <Spin size="small" />
            </div>
          </Option>
        )}
      </Select>
    );
  },
);

export default LazyLoadSelect;
转载自:https://juejin.cn/post/7269966434125070373
评论
请登录