(转)解决antd的select下拉框因为数据量太大造成卡顿的问题

原文链接: https://blog.csdn.net/liuyuhua666/article/details/103703478

参考链接实践时候 我遇到的问题 得到需要注意的事项:
当涉及到多个select下拉框,他们的option的选项不同时,他们共同存储的对象
比如 optionFather的属性是动态添加手动写入的,
this.optionFather['100001'] = [{a:1,b:2},{a1:1,b1:2},{a2:1,b2:2},] Vue是监听不到数组的变化的,
要通过set,将100001这个属性变为动态属性 他的变化 才会触发视图重新渲染。
this.$set(this.optionFather, '100001', value);这种的。

得到
optionFather = {
 '100001': optionSelectList1,
 '100002': optionSelectList2,
  ……
}

相信用过antd的同学基本都用过select下拉框了,这个组件数据量少的时候很好用,但是当数据量大的时候,比如大几百条上千条甚至是几千条的时候就感觉一点都不好用了,卡的我怀疑人生,一点用户体验都没有了。当然这不是我想去优化它的动力,主要是公司业务人员和后端的同事也无法忍受,于是我只能屈从于他们的淫威。。。。想要优化肯定要知道为什么会卡,初步判断就是数据量过大导致渲染option组件的时间过长导致卡顿,于是想要不卡只能限制渲染的数据数量。我的想法是这样的:任何时候都只渲染前100条数据以保证不卡顿,然后当需要搜索的时候对从后台拿到的数据进行过滤,也只取前100条,然后当select框不下拉的时候也就是失焦的时候将数据回复原样。下面是我的具体实现:

  1. 先从后台拿到数据,保存到变量fundList中(作为数据源,永远不改动),然后取其中的前100条数据保存到fundList_中,用来下拉框的数据渲染
{fundList_.map(item => <Option key={item.fund} value={item.fund}>{item.name}</Option>)}

  1. 这是整个select组件:
<Select
   mode="multiple"
    maxTagCount={0}
    placeholder="请选择"
    showSearch={true}
    onBlur={this.handleOnBlur}
    onSearch={this.handleOnSearch}
    allowClear={true}
    onChange={(value)=>{this.modalChangeSelect(value,'1')}}
    style={{width:'223px'}}
    value={record['1']||undefined}
    disabled={this.state.visibleType==='修改'?true:false}
>
    {fundList_.map(item => <Option key={item.fund} value={item.fund}>{item.name}</Option>)}
</Select>

  1. 然后写search里面的功能
handleOnSearch = value => {
   // 函数节流,防止数据频繁更新,每300毫秒才搜索一次
   let that = this
   if (!this.timer) {
     this.timer = setTimeout(function(){
       that.searchValue(value)
       that.timer = null
     },300)
   }
 }
searchValue = (value) => {
    const datas = [] 
    const {fundList} = this.state
    // 对fundList进行遍历,将符合搜索条件的数据放入datas中
    fundList.forEach(item => {
      if (item.name.indexOf(value) > -1) {
        datas.push(item)
      }
    })
    // 然后只显示符合搜索条件的所有数据中的前100条
    this.setState({fundList_: datas.slice(0,100)})
}

  1. 当select失焦的时候,将数据恢复原样(只显示fundList中的前100条数据):
handleOnBlur = () => {
   this.setState({fundList_: this.state.fundList.slice(0,100)})
 }

到此这个功能就大体实现了,已经不存在卡顿的问题了,但是这个方法并不是完美的,这不,业务就说了,你只显示了前100条数据,但是我有时候不通过搜索功能查找某条数据,我要在所有的数据里面直接找到那条数据(业务也不嫌累。。。),我要显示所有的数据。这下就难办了,因为卡顿就是渲染太多的数据造成的,所以还是不能一次性渲染所有的数据,然后怎么办呢,我也不知道怎么办呐。于是上网搜索了一下别人碰到相关问题的解决办法,于是还真的找到了。
思路是这样的:同样是先只展示前100条数据(这个没办法,想要不卡只能这样),然后当滚动条滚到第100条数据也就是滚到底部的时候再增加100条,就这样一直到展示所有的数据,下面是具体的实现步骤:
1、先造点假数据:

const data = [];
for (let i = 0; i < 1000; i++) {
  data.push(`test${i}`);
}
// 一开始只展示前100条数据
const data_ = data.slice(0, 100);

2、渲染出来

<Select
  showSearch
  allowClear
  onPopupScroll={this.handleScroll}
  style={{ width: 200 }}
  placeholder="Select a person"
  optionFilterProp="children"
  onChange={this.onChange}
  onFocus={this.onFocus}
  onBlur={this.onBlur}
  onSearch={this.onSearch}
  filterOption={(input, option) =>
    option.props.children.toLowerCase().indexOf(input.toLowerCase()) >= 0
  }
>
  {optionData.map(item => (
    <Option value={item}>{item}</Option>
  ))}
</Select>

3、写滚动条滚动的功能
在这里就要说一下select里面的一个参数了,就是 onPopupScroll,以前没有注意到,看到别人提醒的时候才发现。有了它就可以实现滚动实时刷新数据了。


在这里插入图片描述

然后写滚动的功能

handleScroll = e => {
   e.persist();
   const { target } = e;
   // scrollHeight:代表包括当前不可见部分的元素的高度
   // scrollTop:代表当有滚动条时滚动条向下滚动的距离,也就是元素顶部被遮住的高度
   // clientHeight:包括padding但不包括border、水平滚动条、margin的元素的高度
   const rmHeight = target.scrollHeight - target.scrollTop;
   const clHeight = target.clientHeight;
   // 当下拉框失焦的时候,也就是不下拉的时候
   if (rmHeight === 0 && clHeight === 0) {
     this.setState({ scrollPage: 1 });
   } else {
   // 当下拉框下拉并且滚动条到达底部的时候
   // 可以看成是分页,当滚动到底部的时候就翻到下一页
     if (rmHeight < clHeight + 5) {
       const { scrollPage } = this.state;
       this.setState({ scrollPage: scrollPage + 1 });
       //调用处理数据的函数增加下一页的数据
       this.loadOption(scrollPage + 1);
     }
   }
 };
 loadOption = pageIndex => {
    const { pageSize, keyWords } = this.state;
    // 通过每页的数据条数和页数得到总的需要展示的数据条数
    const newPageSize = pageSize * (pageIndex || 1);
    let newOptionsData = [],len; // len 能展示的数据的最大条数
    if (data.length > newPageSize) {
      // 如果总数据的条数大于需要展示的数据
      len = newPageSize;
    } else {
      // 否则
      len = data.length;
    }
    // 如果有搜索的话,就走这里
    if (!!keyWords) {
      let data_ = data.filter(item => item.indexOf(keyWords) > -1) || [];
      data_.forEach((item, index) => {
        if (index < len) {
          newOptionsData.push(item);
        }
      });
    } else {
      data.forEach((item, index) => {
        if (index < len) {
          newOptionsData.push(item);
        }
      });
    }
    this.setState({ optionData: newOptionsData });
  };

4、搜索功能:
和我刚开始的一样

onSearch = val => {
    console.log("search:", val);
    if (!this.timer) {
      const that = this;
      this.timer = setTimeout(function() {
        that.searchValue(val);
        that.timer = null;
      }, 300);
    }
    this.setState({ keyWords: val });
  };
  searchValue = value => {
    let data_ = data.filter(item => item.indexOf(value) > -1);
    if (data_.length > 100 || value === "") {
      data_ = data_.slice(0, 100);
    }
    this.setState({ optionData: data_ });
  };

5、 然后失焦的时候:

handleOnBlur = () => {
   this.setState({fundList_: this.state.fundList.slice(0,100)})
 }

总的代码:

import React from "react";
import ReactDOM from "react-dom";
import "antd/dist/antd.css";
import "./index.css";
import { Select } from "antd";

const { Option } = Select;

const data = [];
// let pageSize = 100,scrollPage = 1,keyWords = '',optionData = [];
for (let i = 0; i < 1000; i++) {
 data.push(`test${i}`);
}

const data_ = data.slice(0, 100);

class App extends React.Component {
 state = {
   pageSize: 100,
   scrollPage: 1,
   keyWords: "",
   optionData: data_
 };

 onChange = value => {
   console.log(`selected ${value}`);
 };

 onBlur = () => {
   console.log("blur");
   this.setState({ optionData: data_ });
 };

 onFocus = () => {
   console.log("focus");
 };

 onSearch = val => {
   console.log("search:", val);
   if (!this.timer) {
     const that = this;
     this.timer = setTimeout(function() {
       that.searchValue(val);
       that.timer = null;
     }, 300);
   }
   this.setState({ keyWords: val });
 };
 searchValue = value => {
   let data_ = data.filter(item => item.indexOf(value) > -1);
   if (data_.length > 100 || value === "") {
     data_ = data_.slice(0, 100);
   }
   this.setState({ optionData: data_ });
 };
 loadOption = pageIndex => {
   const { pageSize, keyWords } = this.state;
   const newPageSize = pageSize * (pageIndex || 1);
   let newOptionsData = [],
     len;
   if (data.length > newPageSize) {
     len = newPageSize;
   } else {
     len = data.length;
   }
   if (!!keyWords) {
     let data_ = data.filter(item => item.indexOf(keyWords) > -1) || [];
     data_.forEach((item, index) => {
       if (index < len) {
         newOptionsData.push(item);
       }
     });
   } else {
     data.forEach((item, index) => {
       if (index < len) {
         newOptionsData.push(item);
       }
     });
   }
   this.setState({ optionData: newOptionsData });
 };

 handleScroll = e => {
   e.persist();
   const { target } = e;
   const rmHeight = target.scrollHeight - target.scrollTop;
   const clHeight = target.clientHeight;
   if (rmHeight === 0 && clHeight === 0) {
     this.setState({ scrollPage: 1 });
   } else {
     if (rmHeight < clHeight + 5) {
       console.log(111, rmHeight, clHeight);
       const { scrollPage } = this.state;
       this.setState({ scrollPage: scrollPage + 1 });
       // scrollPage = scrollPage + 1;
       this.loadOption(scrollPage + 1);
     }
   }
   // console.log(e.target)
 };

 render() {
   const { optionData } = this.state;
   console.log(optionData.length);
   return (
     <Select
       showSearch
       allowClear
       onPopupScroll={this.handleScroll}
       style={{ width: 200 }}
       placeholder="Select a person"
       optionFilterProp="children"
       onChange={this.onChange}
       onFocus={this.onFocus}
       onBlur={this.onBlur}
       onSearch={this.onSearch}
       filterOption={(input, option) =>
         option.props.children.toLowerCase().indexOf(input.toLowerCase()) >= 0
       }
     >
       {optionData.map(item => (
         <Option value={item}>{item}</Option>
       ))}
     </Select>
   );
 }
}

ReactDOM.render(<App />, document.getElementById("container"));

其实两个方法各有优劣,第一种的话没有卡顿,但是展示的数据量对于有些人来说可能不太够,而第二种方法呢虽然下拉没有卡顿,但是当滚动了很多数据的时候滚动就会有点卡并且选择某条数据也会有点卡。所以看场景了。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,088评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,715评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,361评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,099评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 60,987评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,063评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,486评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,175评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,440评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,518评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,305评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,190评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,550评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,880评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,152评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,451评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,637评论 2 335