antd的select下拉框因为数据量太大造成卡顿的解决方式
作者:灯光下的投影
相信用过antd的同学基本都用过select下拉框了,这个组件数据量少的时候很好用,但是当数据量大的时候,比如大几百条上千条甚至是几千条的时候就感觉一点都不好用了,卡的我怀疑人生,一点用户体验都没有了。
当然这不是我想去优化它的动力,主要是公司业务人员和后端的同事也无法忍受,于是我只能屈从于他们的淫威。。。。
想要优化肯定要知道为什么会卡,初步判断就是数据量过大导致渲染option组件的时间过长导致卡顿,于是想要不卡只能限制渲染的数据数量。
我的想法是这样的:任何时候都只渲染前100条数据以保证不卡顿,然后当需要搜索的时候对从后台拿到的数据进行过滤,也只取前100条,然后当select框不下拉的时候也就是失焦的时候将数据回复原样。
下面是我的具体实现:
先从后台拿到数据,保存到变量fundList中(作为数据源,永远不改动),然后取其中的前100条数据保存到fundList_中,用来下拉框的数据渲染
{fundList_.map(item => <Option key={item.fund} value={item.fund}>{item.name}</Option>)}
这是整个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>
然后写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)}) }
当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"));
其实两个方法各有优劣,第一种的话没有卡顿,但是展示的数据量对于有些人来说可能不太够,而第二种方法呢虽然下拉没有卡顿,但是当滚动了很多数据的时候滚动就会有点卡并且选择某条数据也会有点卡。所以看场景了。
补充知识:VUE element select 选项内容显示过长问题
我就废话不多说了,大家还是直接看代码吧~
<style> .el-select__tags-text { display: inline-block; max-width: 120px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } .el-select .el-tag__close.el-icon-close { top: -7px; } </style>
以上这篇antd的select下拉框因为数据量太大造成卡顿的解决方式就是小编分享给大家的全部内容了,希望能给大家一个参考,也希望大家多多支持脚本之家。