(轉)解決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"));

其實兩個方法各有優劣,第一種的話沒有卡頓,但是展示的數據量對于有些人來說可能不太夠,而第二種方法呢雖然下拉沒有卡頓,但是當滾動了很多數據的時候滾動就會有點卡并且選擇某條數據也會有點卡。所以看場景了。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。