Concis组件库封装——List列表

1,002 阅读2分钟

您好,如果喜欢我的文章,可以关注我的公众号「量子前端」,将不定期关注推送前端好文~

列表组件的封装其实和Table有一定的相似,都是数据一列一列展示,因此列表组件也用到了懒加载和虚拟列表对大数据量时提供了解决方案。

组件库文档: 在这里插入图片描述 List.tsx:

import React, { createContext, useMemo, useState, useEffect, useRef } from 'react';
import { listProps, listHeaderStyle, listContentStyle } from './interface';
import Item from './item';
import './style/list.module.less';

export const ctx = createContext<any>({} as any); //顶层通信装置
const List = (props: listProps) => {
  const {
    style = {},
    dataSource,
    render,
    header,
    size = 'default',
    lazyLoad = false,
    defaultShowNum = 5,
    virtualListProps,
    virtualShowNum = 5,
  } = props;

  const contextProps = {
    size,
  };
  const [formatDataSrouce, setFormatDataSource] = useState([...dataSource]); //处理过的数据源
  const [scrollTop, setScrollTop] = useState(0);
  const listItemHeight = useRef<any>(null);

  const listContentRef = useRef<any>(null);
  const victurlListContentRef = useRef<any>(null);

  useEffect(() => {
    if (lazyLoad && defaultShowNum) {
      setFormatDataSource((old) => {
        old = dataSource.slice(0, defaultShowNum);
        return [...old];
      });
    } else if (virtualListProps) {
      let rowHeight = document.querySelector('.list-item')?.clientHeight as any;
      switch (size) {
        case 'default':
          rowHeight += 26;
          break;
        case 'small':
          rowHeight += 18;
          break;
        case 'large':
          rowHeight += 34;
          break;
      }
      listItemHeight.current = rowHeight;
      setFormatDataSource((old) => {
        old = dataSource.slice(0, virtualShowNum + 2);
        return [...old];
      });
    }
  }, []);

  const listHeaderStyle = useMemo(() => {
    //头部样式
    const defaultStyles: listHeaderStyle = {};
    switch (size) {
      case 'default':
        defaultStyles.padding = '12px 20px';
        break;
      case 'small':
        defaultStyles.padding = '8px 20px';
        break;
      case 'large':
        defaultStyles.padding = '16px 20px';
        break;
    }
    return defaultStyles;
  }, [size]);

  const listStyle = useMemo(() => {
    //表整体样式
    return style;
  }, [style]);
  const listContentStyle = useMemo(() => {
    //表正文样式
    const returnStyle: listContentStyle = {};
    if (lazyLoad && defaultShowNum) {
      returnStyle.height = '400px';
      returnStyle.overflow = 'scroll';
    }
    return returnStyle;
  }, [lazyLoad, defaultShowNum]);
  const scrollList = () => {
    const { scrollHeight, clientHeight, scrollTop } = listContentRef.current as any;
    const bottomTran = scrollHeight - clientHeight - scrollTop; //距离底部距离
    if (bottomTran === 0) {
      setTimeout(() => {
        setFormatDataSource((old) => {
          old = dataSource.slice(0, old.length + defaultShowNum);
          return [...old];
        });
      }, 500);
    }
  };
  const victurlScroll = () => {
    const startIndex = Math.floor(victurlListContentRef.current.scrollTop / listItemHeight.current);
    setScrollTop(victurlListContentRef.current.scrollTop);
    setFormatDataSource((old) => {
      old = dataSource.slice(startIndex, startIndex + virtualShowNum + 2);
      return [...old];
    });
  };

  return (
    <ctx.Provider value={contextProps}>
      <div className="rList" style={listStyle}>
        <div className="list-header" style={listHeaderStyle}>
          {header}
        </div>
        {virtualListProps ? (
          <div
            className="victurl-list-content"
            style={{ height: virtualShowNum * listItemHeight.current + 'px' }}
            ref={victurlListContentRef}
            onScroll={victurlScroll}
          >
            <div
              className="victurl-relly-content"
              style={{
                height: dataSource.length * listItemHeight.current - scrollTop + 'px',
                transform: `translate(0, ${scrollTop}px)`,
              }}
            >
              {formatDataSrouce.map(render)}
            </div>
          </div>
        ) : (
          <div
            className="list-content"
            style={listContentStyle}
            ref={listContentRef}
            onScroll={scrollList}
          >
            {formatDataSrouce.map(render)}
          </div>
        )}
      </div>
    </ctx.Provider>
  );
};

interface ForwardRefListType
  extends React.ForwardRefExoticComponent<
    React.PropsWithoutRef<listProps> & React.RefAttributes<HTMLDivElement>
  > {
  <T = any>(
    props: React.PropsWithChildren<listProps<T>> & {
      ref?: React.Ref<HTMLDivElement>;
    },
  ): React.ReactElement;
  Item: typeof Item;
}

const ListComponent = React.forwardRef<HTMLDivElement, listProps>(List) as ForwardRefListType;
ListComponent.Item = Item;
ListComponent.displayName = 'List';

export default ListComponent;

Item.tsx:

import React, { FC, memo, useMemo, useContext } from 'react';
import { listItemProps } from './interface';
import { ctx } from './index';
import './style/item.module.less';

const Item: FC<listItemProps> = (props) => {
  const { children, style = {} } = props;
  const { size } = useContext(ctx);

  const listItemStyle = useMemo(() => {
    const defaultStyles = style;
    switch (size) {
      case 'default':
        defaultStyles.padding = '13px 20px';
        break;
      case 'small':
        defaultStyles.padding = '9px 20px';
        break;
      case 'large':
        defaultStyles.padding = '17px 20px';
        break;
    }
    return defaultStyles;
  }, [size]);
  return (
    <div className="list-item" style={listItemStyle}>
      {children}
    </div>
  );
};

export default memo(Item);

最后留一下React-View-UI组件库线上地址吧~

开源不易,欢迎学习和体验,喜欢请多多支持,有问题请留言。