功能简介

antd 的 Select 组件不支持大数据量的下拉列表渲染,下拉列表数量太多会出现性能问题, SuperSelect 基于 antd 封装实现,替换原组件下拉列表,只渲染几十条列表数据,随下拉列表滚动动态刷新可视区列表状态,实现大数据量列表高性能渲染。

  • 特性

    1. 基于 antd Select 组件,不修改组件用法
    2. 替换 antd Select 组件的下拉列表部分实现动态渲染列表
    3. 初步测试 10w 条数据不卡顿
  • 实现方案

    1. 使用 antd Select dropdownRender 方法自定义原组件下拉列表部分
    2. 对自定义列表项绑定原 Select 组件的各项方法和回调函数支持
    3. 同步使用 antd 组件下拉列表样式

在线地址

使用

基本使用同 antd Select,只是使用 SuperSelect 代替 Select

import SuperSelect from 'components/SuperSelect';
import { Select } from 'antd';
const Option = Select.Option;const Example = () => {const children = [];for (let i = 0; i < 100000; i++) {children.push(<Option value={i + ''} key={i}>{i}</Option>);}return (<SuperSelectshowSearch// mode="multiple"// onChange={onChange}// onSearch={onSearch}// onSelect={onSelect}>{children}</SuperSelect>);
};
复制代码

问题

  • 多选模式选择后鼠标点击输入框中删除等图标时不能直接 hover 时获取焦点直接删除,需要点击两次

    Warning: the children of `Select` should be `Select.Option`
    or `Select.OptGroup`, instead of `li`
    复制代码
  • 重写的下拉菜单没有 isSelectOption(rc-select 源码判断下拉列表元素)属性,控制台会有 warning 提示下拉列表元素不符合 Select 组件要求

大佬们有啥更好的做法或建议请多多指教

附上代码

import React, { PureComponent, Fragment } from 'react';
import { Select } from 'antd';// 页面实际渲染的下拉菜单数量,实际为 2 * ITEM_ELEMENT_NUMBER
const ITEM_ELEMENT_NUMBER = 20;
// Select size 配置
const ITEM_HEIGHT_CFG = {small: 24,large: 40,default: 32,
};class Wrap extends PureComponent {state = {list: this.props.list,allHeight: this.props.allHeight,};reactList = (list, allHeight) => this.setState({ list, allHeight });render() {const { list } = this.state;const { notFoundContent } = this.props;// 搜索下拉列表为空时显示 no dataconst noDataEle = (<lirole="option"unselectable="on"className="ant-select-dropdown-menu-item ant-select-dropdown-menu-item-disabled"aria-disabled="true"aria-selected="false"><div className="ant-empty ant-empty-normal ant-empty-small"><div className="ant-empty-image"><imgalt="No Data"src="data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNjQiIGhlaWdodD0iNDEiIHZpZXdCb3g9IjAgMCA2NCA0MSIgIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CiAgPGcgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoMCAxKSIgZmlsbD0ibm9uZSIgZmlsbC1ydWxlPSJldmVub2RkIj4KICAgIDxlbGxpcHNlIGZpbGw9IiNGNUY1RjUiIGN4PSIzMiIgY3k9IjMzIiByeD0iMzIiIHJ5PSI3Ii8+CiAgICA8ZyBmaWxsLXJ1bGU9Im5vbnplcm8iIHN0cm9rZT0iI0Q5RDlEOSI+CiAgICAgIDxwYXRoIGQ9Ik01NSAxMi43Nkw0NC44NTQgMS4yNThDNDQuMzY3LjQ3NCA0My42NTYgMCA0Mi45MDcgMEgyMS4wOTNjLS43NDkgMC0xLjQ2LjQ3NC0xLjk0NyAxLjI1N0w5IDEyLjc2MVYyMmg0NnYtOS4yNHoiLz4KICAgICAgPHBhdGggZD0iTTQxLjYxMyAxNS45MzFjMC0xLjYwNS45OTQtMi45MyAyLjIyNy0yLjkzMUg1NXYxOC4xMzdDNTUgMzMuMjYgNTMuNjggMzUgNTIuMDUgMzVoLTQwLjFDMTAuMzIgMzUgOSAzMy4yNTkgOSAzMS4xMzdWMTNoMTEuMTZjMS4yMzMgMCAyLjIyNyAxLjMyMyAyLjIyNyAyLjkyOHYuMDIyYzAgMS42MDUgMS4wMDUgMi45MDEgMi4yMzcgMi45MDFoMTQuNzUyYzEuMjMyIDAgMi4yMzctMS4zMDggMi4yMzctMi45MTN2LS4wMDd6IiBmaWxsPSIjRkFGQUZBIi8+CiAgICA8L2c+CiAgPC9nPgo8L3N2Zz4K"/></div><p className="ant-empty-description">{notFoundContent || '没有匹配到数据'}</p></div></li>);return (<div style={{ overflow: 'hidden', height: this.state.allHeight }}><ulrole="listbox"className="ant-select-dropdown-menu  ant-select-dropdown-menu-root ant-select-dropdown-menu-vertical"style={{height: this.state.allHeight,maxHeight: this.state.allHeight,overflow: 'hidden',}}tabIndex="0">{list.length > 0 ? list : noDataEle}</ul></div>);}
}export default class SuperSelect extends PureComponent {constructor(props) {super(props);const { mode, defaultValue, value } = props;this.isMultiple = ['tags', 'multiple'].includes(mode);// 设置默认 valuelet defaultV = this.isMultiple ? [] : '';defaultV = value || defaultValue || defaultV;this.state = {children: props.children || [],filterChildren: null,value: defaultV,};// 下拉菜单项行高this.ITEM_HEIGHT = ITEM_HEIGHT_CFG[props.size || 'default'];// 可视区 dom 高度this.visibleDomHeight = this.ITEM_HEIGHT * ITEM_ELEMENT_NUMBER;// 滚动时重新渲染的 scrollTop 判断值,大于 reactDelta 则刷新下拉列表this.reactDelta = (this.visibleDomHeight * 2) / 3;// 是否拖动滚动条快速滚动状态this.isStopReact = false;// 上一次滚动的 scrollTop 值this.prevScrollTop = 0;this.scrollTop = 0;}componentDidUpdate(prevProps, prevStates) {if (prevProps.children !== this.props.children) {const { mode, defaultValue, value } = this.props;this.isMultiple = ['tags', 'multiple'].includes(mode);// 更新时设置默认 valuelet defaultV = this.isMultiple ? [] : '';defaultV = value || defaultValue || defaultV;this.setState({children: this.props.children || [],filterChildren: null,value: defaultV,});}}getItemStyle = i => ({position: 'absolute',top: this.ITEM_HEIGHT * i,width: '100%',height: this.ITEM_HEIGHT,});addEvent = () => {this.scrollEle = document.querySelector('.my-select');// 下拉菜单未展开时元素不存在if (!this.scrollEle) return;this.scrollEle.addEventListener('scroll', this.onScroll, false);};onScroll = () => this.throttleByHeight(this.onScrollReal);onScrollReal = () => {this.allList = this.getUseChildrenList();this.showList = this.getVisibleOptions();this.prevScrollTop = this.scrollTop;// 重新渲染列表组件 Wraplet allHeight = this.allList.length * this.ITEM_HEIGHT || 100;this.wrap.reactList(this.showList, allHeight);};throttleByHeight = () => {this.scrollTop = this.scrollEle.scrollTop;// 滚动的高度let delta = this.prevScrollTop - this.scrollTop;delta = delta < 0 ? 0 - delta : delta;// TODO: 边界条件优化, 滚动约 2/3 可视区 dom 高度时刷新 domdelta > this.reactDelta && this.onScrollReal();};// 列表可展示所有 childrengetUseChildrenList = () => this.state.filterChildren || this.state.children;getStartAndEndIndex = () => {// 滚动后显示在列表可视区中的第一个 item 的 indexconst showIndex = Number((this.scrollTop / this.ITEM_HEIGHT).toFixed(0));const startIndex =showIndex - ITEM_ELEMENT_NUMBER < 0? 0: showIndex - ITEM_ELEMENT_NUMBER / 2;const endIndex = showIndex + ITEM_ELEMENT_NUMBER;return { startIndex, endIndex };};getVisibleList = () => {// 搜索时使用过滤后的列表const { startIndex, endIndex } = this.getStartAndEndIndex();// 渲染的 listreturn this.allList.slice(startIndex, endIndex);};getVisibleOptions = () => {const visibleList = this.getVisibleList();const { startIndex } = this.getStartAndEndIndex();// 显示中的列表元素添加相对定位样式return visibleList.map((item, i) => {let props = { ...item.props };const text = props.children;const realIndex = startIndex + Number(i);const key = props.key || realIndex;const { value } = this.state;const valiValue = text || props.value;const isSelected =value && value.includes? value.includes(valiValue): value == valiValue;const classes = `ant-select-dropdown-menu-item ${isSelected ? 'ant-select-dropdown-menu-item-selected' : ''}`;// antd 原素,下拉列表项右侧 √ iconconst selectIcon = (<iaria-label="icon: check"className="anticon anticon-check ant-select-selected-icon"><svgviewBox="64 64 896 896"className=""data-icon="check"width="1em"height="1em"fill="currentColor"aria-hidden="true"focusable="false"><path d="M912 190h-69.9c-9.8 0-19.1 4.5-25.1 12.2L404.7 724.5 207 474a32 32 0 0 0-25.1-12.2H112c-6.7 0-10.4 7.7-6.3 12.9l273.9 347c12.8 16.2 37.4 16.2 50.3 0l488.4-618.9c4.1-5.1.4-12.8-6.3-12.8z" /></svg></i>);props._childrentext = text;return (<liclassName={classes}key={key}onMouseDown={() => this.onClick(props, item)}{...props}style={this.getItemStyle(realIndex)}>{text}{/* 多选项选中状态 √ 图标 */}{this.isMultiple ? selectIcon : null}</li>);});};render() {let {children,dropdownStyle,optionLabelProp,notFoundContent,...props} = this.props;this.allList = this.getUseChildrenList();this.showList = this.getVisibleOptions();let allHeight = this.allList.length * this.ITEM_HEIGHT || 100;dropdownStyle = {maxHeight: '250px',...dropdownStyle,overflow: 'auto',position: 'relative',};const { value } = this.state;// 判断处于 antd Form 中时不自动设置 valuelet _props = { ...props };// 先删除 value,再手动赋值,防止空 value 影响 placeholderdelete _props.value;if (!this.props['data-__field'] && value && value.length > 0) {_props.value = value;}// 设置显示在输入框的文本,替换 children 为自定义 childrentext,默认 children 会包含 √ iconoptionLabelProp = optionLabelProp ? optionLabelProp : '_childrentext';optionLabelProp =optionLabelProp === 'children' ? '_childrentext' : optionLabelProp;return (<Select{..._props}onSearch={this.onSearch}onChange={this.onChange}onSelect={this.onSelect}dropdownClassName="my-select"optionLabelProp={optionLabelProp}dropdownStyle={dropdownStyle}onDropdownVisibleChange={this.setSuperDrowDownMenu}ref={ele => (this.select = ele)}dropdownRender={menu => (<Fragment><Wrapref={ele => (this.wrap = ele)}allHeight={allHeight}list={this.showList}notFoundContent={notFoundContent}/></Fragment>)}>{this.showList}</Select>);}// 须使用 setTimeout 确保在 dom 加载完成之后添加事件setSuperDrowDownMenu = () => {this.allList = this.getUseChildrenList();this.allList = this.getUseChildrenList();if (!this.eventTimer) {this.eventTimer = setTimeout(() => this.addEvent(), 0);} else {let allHeight = this.allList.length * this.ITEM_HEIGHT || 100;// 下拉列表单独重新渲染this.wrap && this.wrap.reactList(this.showList, allHeight);}};/*** 替换了 antd Select 的下拉列表,手动实现下拉列表项的点击事件,* 绑定原组件的各项事件回调* itemProps: li react 元素的 props* item: li 元素*/onClick = (itemProps, item) => {let { value } = itemProps;const { onDeselect } = this.props;let newValue = this.state.value || [];let option = item;// 多选if (this.isMultiple) {newValue = [...newValue];// 点击选中项取消选中操作if (newValue.includes(value)) {newValue = newValue.filter(i => i !== value);onDeselect && onDeselect(value, item);} else {newValue.push(value);}// 获取原 onChange 函数第二个参数 options,react 元素数组option = this.state.children.filter(i =>newValue.includes(i.props.value));} else {newValue = value;}// 多选模式点击选择后下拉框持续显示this.isMultiple && this.focusSelect();this.onChange(newValue, option);this.onSelect(newValue, option);};// 非 antd select 定义元素点击后会失去焦点,手动再次获取焦点防止多选时自动关闭focusSelect = () => setTimeout(() => this.select.focus(), 0);// 绑定 onSelect 事件onSelect = (v, opt) => {const { onSelect } = this.props;onSelect && onSelect(v, opt);};onChange = (value, opt) => {// 删除选中项时保持展开下拉列表状态if (Array.isArray(value) && value.length < this.state.value.length) {this.focusSelect();}const { showSearch, onChange, autoClearSearchValue } = this.props;if (showSearch || this.isMultiple) {// 搜索模式下选择后是否需要重置搜索状态if (autoClearSearchValue !== false) {this.setState({ filterChildren: null }, () => {// 搜索成功后重新设置列表的总高度this.setSuperDrowDownMenu();});}}this.setState({ value });onChange && onChange(value, opt);};onSearch = v => {let { showSearch, onSearch, filterOption, children } = this.props;if (showSearch && filterOption !== false) {// 须根据 filterOption(如有该自定义函数)手动 filter 搜索匹配的列表let filterChildren = null;if (typeof filterOption === 'function') {filterChildren = children.filter(item => filterOption(v, item));} else if (filterOption === undefined) {filterChildren = children.filter(item =>this.filterOption(v, item));}// 设置下拉列表显示数据this.setState({ filterChildren: v === '' ? null : filterChildren },() => {// 搜索成功后需要重新设置列表的总高度this.setSuperDrowDownMenu();});}onSearch && onSearch(v);};filterOption = (v, option) => {// 自定义过滤对应的 option 属性配置const filterProps = this.props.optionFilterProp || 'value';return `${option.props[filterProps]}`.indexOf(v) >= 0;};componentWillUnmount() {this.removeEvent();}removeEvent = () => {if (!this.scrollEle) return;this.scrollEle.removeEventListener('scroll', this.onScroll, false);};
}复制代码

支持大数据渲染下拉列表组件开发 SuperSelect(基于antd Select)相关推荐

  1. vue 大数据 渲染_技术专栏 | DMap——实战Vue百万条数据渲染表格组件开发

    作者:TalkingData 李志刚 本文由TalkingData原创,转载请获取授权. 李志刚:近几个月在开发一个基于Vue的数据可视化分析辅助应用---DMap(谛听),一套为数据分析师和数据科学 ...

  2. DMap(谛听)——实战Vue百万条数据渲染表格组件开发

    近几个月在开发一个基于Vue的数据可视化分析辅助应用---DMap(谛听),一套为数据分析师和数据科学家提供的基于位置大数据分析的工具,旨在提高数据分析效率,降低获取多数据并行分析成本,简化大屏和数据 ...

  3. 大数据平台常用组件_这款大数据智能服务平台火了!全自动化配置30+款开源大数据组件...

    在互联网市场的头部效应下,企业所面临的竞争压力越来越大,如何有效解决获客成本高.用户黏性低.变现能力弱等问题,正是越来越多的企业开始构建大数据平台的初衷.但由于大数据解决方案所涉及的组件错综复杂.技术 ...

  4. 大数据生态圈常用组件(二):概括介绍、功能特性、适用场景

    三更灯火五更鸡,正是男儿读书时. 小编整理了一些常用的大数据组件,使用场景及功能特性,希望对后浪有所帮助. 分类 名称 简介 功能特点 使用场景 大数据存储 HDFS HDFS是一个分布式的文件系统, ...

  5. 大数据全栈式开发语言 – Python

    前段时间,ThoughtWorks在深圳举办一次社区活动上,有一个演讲主题叫做"Fullstack JavaScript",是关于用JavaScript进行前端.服务器端,甚至数据 ...

  6. 如何让热点图支持大数据

    转自fu*k原文如何让热点图支持大数据 所谓的热点图,是图1)构建一张灰度图,图2)在每个热点的位置上绘制并叠加形成灰色的热点图,图3)根据颜色表生成热点图.不难看出,最核心的是图2的过程.详情参考& ...

  7. 大数据权限管理组件Apache Ranger简介和原理

    大数据权限管理组件Apache Ranger简介和原理 一.什么是Ranger 二.Ranger的管理页面和Ranger支持的框架 三.Ranger的目标 四.Ranger架构 五.Ranger的工作 ...

  8. Java语言开发在线美食推荐网 美食推荐系统 基于用户、物品的协同过滤推荐算法实现 SSM(Spring+SpringMVC+Mybatis框架 人工智能、大数据、机器学习项目开发

    Java语言开发在线美食推荐网 美食推荐系统 基于用户.物品的协同过滤推荐算法实现 SSM(Spring+SpringMVC+Mybatis框架 人工智能.大数据.机器学习项目开发FoodRecomm ...

  9. BigData:MaxCompute大数据计算服务(阿里巴巴开发/原ODPS/云计算分布式)的简介(基本概念/功能/流程图)、使用方法之详细攻略

    BigData:MaxCompute大数据计算服务(阿里巴巴开发/原ODPS/云计算分布式)的简介(基本概念/功能/流程图).使用方法之详细攻略 目录 背景-传统分布式计算的弊端 MaxCompute ...

最新文章

  1. Spring AOP + Redis解决重复提交的问题
  2. 我就不信看完这篇你还搞不懂信息熵
  3. R 变量名开头不能为数字
  4. 文件搜索工具everything
  5. ITK:计算矢量图像的梯度
  6. 报错:out cannot be resolved【已解决】
  7. 和我一起写矩阵类(一)
  8. 常用的色彩名、色彩值的对照表
  9. 五个免费国外流量统计工具
  10. webpack 分离css html,【已解决】ReactJS中Webpack打包时分离css
  11. 超快的HTML5 2D渲染引擎Pixi.js入门
  12. dash视频服务器本地搭建 (初探)
  13. 【SysML精粹】系统建模语言概览
  14. 【软件工具】--- 软件安装管家目录
  15. mob做php短信接口,【PHP】短信接口(正则匹配)
  16. SpringBoot系列
  17. HDR视频色调映射算法(之四:Display adaptive TMO)
  18. 安卓app单webview改为多webview加载网页
  19. [Python] GDAL/OGR操作矢量数据(shp、GeoJSON)
  20. 宁夏事业单位计算机类面试题,2019年宁夏事业单位面试试题:经典问题解析

热门文章

  1. 京瓷2010复印a4内容不全_百万畅销书活法的原点,稻盛和夫从未公开的京瓷秘籍...
  2. SAP RETAIL 使用MM41创建的物料不能使用MMSC扩展其存储地点
  3. 机器学习泰斗迈克尔 · 乔丹:不是什么都叫AI的
  4. SAP PP ECR的Profile规定了用它可以修改哪些数据对象
  5. SAP PM 入门系列4 - 如何手工触发一个新的PM检验批?
  6. 雍正继承帝位,给职场中的我们的启示 --- 我看电视剧《雍正王朝》
  7. SAP EWM - 其他主数据 - 运输主数据-2
  8. 深度 | 学习如何学习的算法:简述元学习研究方向现状
  9. ​​《自然》2020年十大科学发现出炉:病毒,冷冻电镜与快速射电暴
  10. 卫星发现,这里用十年逆转了千年!