現象:
我們渲染了9999條數據,由於transfer組件會一次性渲染所有數據,所以一次性渲染這么多,卡個幾十秒很正常好吧。所以懶加載或者分頁是基本操作,方案二是分頁操作。
懶加載的方式可以用EUI的無限滾動:https://element.eleme.cn/#/zh-CN/component/infiniteScroll
即便我們做了懶加載之后,點擊全選依舊是卡頓6秒以上,所以方案一解決的是:即便做了懶加載或者分頁操作后,用戶點擊分頁,依舊會卡頓幾秒的情況。
這個是因為transfer的源碼中‘全選判斷’代碼性能差的原因,方案一就是修改transfer的源碼。
我提交了一個pr,地址是: hhttps://github.com/ElemeFE/element/pull/20282

方案一:復制EUI的transfer組件,然后進行修改,再引入項目目錄
EUI的transfer組件目錄路徑:node_modules\element-ui\packages\transfer,復制文件夾,然后放入vue項目路徑的

在調用EUI的transfer的地方引入公共的組件transfer,
1 <template> 2 <Transfer v-model="value" :data="data"></Transfer> 3 </template> 4 5 <script> 6 import Transfer from '../common/transfer' 7 export default { 8 components:{ 9 Transfer:Transfer 10 }, 11 //省略 12 </script>
開始修改transfer代碼:
打開src/common\transfer\src\transfer-panel.vue的組件,
找到updateAllChecked函數,updateAllChecked函數作用是:我們點擊一個item就需要判斷,看代碼注釋。
1 updateAllChecked() { 2 /* 3 源碼 4 this.checkableData是對象數組 我們需要的是每個對象中的key 5 所以checkableDataKeys保存着對象的key的數組 含義是'可通過點擊進行選擇的item項'的集合 6 */ 7 let start = new Date().getTime(); 8 const checkableDataKeys = this.checkableData.map( 9 item => item[this.keyProp] 10 ); 11 12 this.allChecked = 13 checkableDataKeys.length > 0 && 14 /* 15 從2.4.0到現在都沒改變 誒,不得不說開發團隊是真的忙啊 16 this.checked保存着'用戶通過點擊item選中的item數組' 17 如果this.checked存在着checkableDataKeys的每一項的話,那么allChecked就是true,但凡有一項不存在就為false。allChecked代表是否全部選中了。 18 這里的時間復雜度是n^2,狠垃圾 19 */ 20 checkableDataKeys.every(item => this.checked.indexOf(item) > -1); 21 console.log("updateAllCheckedEnd", new Date().getTime() - start); 22 23 },
來看源碼的耗時:
然后我們開始重寫updateAllChecked函數:
updateAllChecked() { /* 修改 這里就是高效數組中含有另一個數組的元素的算法 構建元素對象 */ let start = new Date().getTime(); let checkableDataKeys = this.checkableData.map((item) => { let keyProps = {}; keyProps[item[this.keyProp]] = true; return keyProps; }); // 通過對象的k-v對應,n(1)的方式尋找數組中是否存在某元素 this.allChecked = checkableDataKeys.length > 0 && this.checked.length > 0 && this.checked.every((item) => checkableDataKeys[item]); // 上面被注釋的源碼是最耗時的,所有一直看耗時就可以了 console.log("updateAllCheckedEnd", new Date().getTime() - start); },
這樣性能就高好多了,其實就是基本的前端算法題,目測EUI的開發者是因為懶才不寫的。
來看修改代碼后的耗時:
明顯快多了。
接下來是文件:\src\common\transfer\src\main.vue,找到addToRight函數
1 addToRight() { 2 let currentValue = this.value.slice(); 3 const itemsToBeMoved = []; 4 const key = this.props.key; 5 let start = new Date().getTime(); 6 // 此處套了兩層循環,耗時長 7 this.data.forEach((item) => { 8 const itemKey = item[key]; 9 if ( 10 this.leftChecked.indexOf(itemKey) > -1 && 11 this.value.indexOf(itemKey) === -1 12 ) { 13 itemsToBeMoved.push(itemKey); 14 } 15 }); 16 console.log("addToRightEnd", new Date().getTime() - start); 17 18 currentValue = 19 this.targetOrder === "unshift" 20 ? itemsToBeMoved.concat(currentValue) 21 : currentValue.concat(itemsToBeMoved); 22 this.$emit("input", currentValue); 23 this.$emit("change", currentValue, "right", this.leftChecked); 24 },
移動選中的耗時:
修改addToRight函數,
1 addToRight() { 2 let start = new Date().getTime(); 3 let currentValue = this.value.slice(); 4 const itemsToBeMoved = []; 5 const key = this.props.key; 6 7 // 修改 8 let leftCheckedKeyPropsObj = {}; 9 this.leftChecked.forEach((item, index) => { 10 leftCheckedKeyPropsObj[item] = true; 11 }); 12 13 let valueKeyPropsObj = {}; 14 this.value.forEach((item, index) => { 15 valueKeyPropsObj[item] = true; 16 }); 17 this.data.forEach((item) => { 18 const itemKey = item[key]; 19 if ( leftCheckedKeyPropsObj[itemKey] && !valueKeyPropsObj[itemKey] ) { 20 itemsToBeMoved.push(itemKey); 21 } 22 }); 23 console.log("addToRightEnd", new Date().getTime() - start); 24 25 currentValue = 26 this.targetOrder === "unshift" 27 ? itemsToBeMoved.concat(currentValue) 28 : currentValue.concat(itemsToBeMoved); 29 this.$emit("input", currentValue); 30 this.$emit("change", currentValue, "right", this.leftChecked); 31 },
移動選中耗時:
耗時明顯減少了,這方案的前提就是懶加載或者分頁,我試了一下10w的數據量,依舊是不錯的。
方案二:分頁操作
分析
checkBox-group有個check數組(用來記錄已經選中的item數組)和renderItem數組(實際渲染的item,由於是分頁,所有不會渲染所有),
如果`check數組`中有`renderItem數組`的一項,那么該項就會被標記為已選,否則是未選。實現原理就是單純的check數組和renderItem數組進行比較。
當用戶點擊全選的時候,check數組變成上萬條數據的數組,此時我們渲染了100條數據,那么就要進行10000x100級別的循環,這就是耗時的原因所在。
其實,頁面只渲染了100條數據,我們沒必要將上萬條數據一次性放入check數組中,我們只需要把這100條數組放入check數組,顯示這100條數據為已選即可。當頁面渲染了更多數據的同時,將新增的數據添加進check數組即可。這樣性能大大提升。
方案
我采用的方案如下:
1. 只顯示100條數據。
2. 下拉顯示下100條數據,上拉顯示上100條數據。
3. 當下拉或者上拉增加渲染數據的同時,把新增數據添加進check數組。
這些只是大致思路,我已經實現了。還有很多細節要處理,想要完善,還得利用對象的鍵值對實現刪除等。