Element-UI的transfer穿梭框组件数据量大解决方案
一、面临问题
数据量大,渲染慢,搜索、勾选、关闭、右移卡顿
二、解决方案
1. 总体思路
改写 Element-UI 的 transfer 穿梭框组件,形成自己的自定义组件
2. 具体步骤
2.1 复制 Element-UI 的 transfer 穿梭框组件出来
a. 对 node_modules/element-ui 中文件结构的理解
- 注册进vue中的element-ui组件使用的是
打包后的文件夹:lib
- packages、src、types是源码,仅供开发者查阅。
- 我们主要需要使用 packages 里的源码
b. 复制出 packages 里的 transfer 组件
- transfer-pannel.vue就是穿梭框的左右板子,main.vue是中控系统,index.js 是组件导出
2.2 编辑transfer-panel.vue
文件,优化全选和单选
a. 优化全选卡顿问题
- 原来代码
updateAllChecked() { const checkableDataKeys = this.checkableData.map(item => item[this.keyProp]); // 这里是O(n^2)的时间复杂度 this.allChecked = checkableDataKeys.length > 0 && checkableDataKeys.every(item => this.checked.indexOf(item) > -1); },
- 修改后代码
updateAllChecked() { let checkObj = {}; this.checked.forEach((item) => { checkObj[item] = true; }); this.allChecked = this.checkableData.length > 0 && this.checked.length > 0 && this.checkableData.every((item) => checkObj[item[this.keyProp]]); },
b. 优化单选卡顿问题
- 源来代码
watch: { checked(val, oldVal) { this.updateAllChecked(); if (this.checkChangeByUser) { // O(n^2)的时间复杂度 const movedKeys = val.concat(oldVal) .filter(v => val.indexOf(v) === -1 || oldVal.indexOf(v) === -1); this.$emit('checked-change', val, movedKeys); } else { this.$emit('checked-change', val); this.checkChangeByUser = true; } }, }
- 修改后代码
watch: { checked(val, oldVal) { this.updateAllChecked(); let newObj = {}; val.every((item)=>{ newObj[item] = true; }); let oldObj = {}; oldVal.every((item)=>{ oldObj[item] = true; }); if (this.checkChangeByUser) { // O(n) const movedKeys = val.concat(oldVal) .filter(v => newObj[v] || oldVal[v]); this.$emit('checked-change', val, movedKeys); } else { this.$emit('checked-change', val); this.checkChangeByUser = true; } }, }
2.3 修改main.vue
文件,优化移动卡顿
- 原来代码
addToRight() { let currentValue = this.value.slice(); const itemsToBeMoved = []; const key = this.props.key; this.data.forEach(item => { const itemKey = item[key]; // O(n^2) if ( this.leftChecked.indexOf(itemKey) > -1 && this.value.indexOf(itemKey) === -1 ) { itemsToBeMoved.push(itemKey); } }); currentValue = this.targetOrder === 'unshift' ? itemsToBeMoved.concat(currentValue) : currentValue.concat(itemsToBeMoved); this.$emit('input', currentValue); this.$emit('change', currentValue, 'right', this.leftChecked); },
- 修改后代码
addToRight() { let currentValue = this.value.slice(); const itemsToBeMoved = []; const key = this.props.key; let leftCheckedKeyPropsObj = {}; this.leftChecked.forEach((item) => { leftCheckedKeyPropsObj[item] = true; }); let valueKeyPropsObj = {}; this.value.forEach((item) => { valueKeyPropsObj[item] = true; }); this.data.forEach((item) => { const itemKey = item[key]; // O(n) if ( leftCheckedKeyPropsObj[itemKey] && !valueKeyPropsObj[itemKey]) { itemsToBeMoved.push(itemKey); } }); currentValue = this.targetOrder === 'unshift' ? itemsToBeMoved.concat(currentValue) : currentValue.concat(itemsToBeMoved); this.$emit('input', currentValue); this.$emit('change', currentValue, 'right', this.leftChecked); },
- 除此之外,还要优化两个computed
computed: { sourceData() { let valueObj = {}; this.value.forEach((item)=>{ valueObj[item] = true; }); return this.data.filter( (item) => !valueObj[item[this.props.key]] ); }, targetData() { if (this.targetOrder === 'original') { let valueObj = {}; this.value.forEach((item)=>{ valueObj[item] = true; }); let data = this.data.filter( (item) => valueObj[item[this.props.key]] ); return data; } else { return this.value.reduce((arr, cur) => { const val = this.dataObj[cur]; if (val) { arr.push(val); } return arr; }, []); } } },
2.4 使用虚拟滚动解决渲染问题
-
npm i vue-virtual-scroll-list
- 创建transfer-checkbox-item.vue组件
内容如下:
<template>
<el-checkbox
class="el-transfer-panel__item"
:label="source[keyProp]"
:disabled="source[disabledProp]">
<option-content :option="source"></option-content>
</el-checkbox>
</template>
<script>
import ElCheckbox from 'element-ui/packages/checkbox';
export default {
name: 'transfer-checkbox-item',
props: {
index: { // index of current item
type: Number
},
source: { // here is: {uid: 'unique_1', text: 'abc'}
type: Object,
default() {
return {};
}
},
keyProp: {
type: String
},
disabledProp: {
type: String
}
},
components: {
ElCheckbox,
OptionContent: {
props: {
option: Object
},
render(h) {
const getParent = vm => {
if (vm.$options.componentName === 'ElTransferPanel') {
return vm;
} else if (vm.$parent) {
return getParent(vm.$parent);
} else {
return vm;
}
};
const panel = getParent(this);
const transfer = panel.$parent || panel;
return panel.renderContent
? panel.renderContent(h, this.option)
: transfer.$scopedSlots.default
? transfer.$scopedSlots.default({ option: this.option })
: <span>{ this.option[panel.labelProp] || this.option[panel.keyProp] }</span>;
}
}
}
};
</script>
- 在
transfer-panel.vue
做修改 引入两个东西
import Item from './transfer-checkbox-item.vue';
import VirtualList from 'vue-virtual-scroll-list';
// 注册VirtualList
components: {
'virtual-list': VirtualList
}
- 初始化定义两个变量
data() {
return {
itemComponent: Item,
virtualListProps: {}
}
}
- 定义一个computed->virtualScroll
computed: {
virtualScroll() {
return this.$parent.virtualScroll;
},
}
- 修改一个computed->keyProp
computed: {
keyProp() {
this.virtualListProps.keyProp = this.props.key || 'key';
return this.props.key || 'key';
}
}
- 修改一个computed->disabledProp
computed: {
disabledProp() {
this.virtualListProps.disabledProp = this.props.disabled || 'disabled';
return this.props.disabled || 'disabled';
}
}
- 原checkbox集合的渲染方式如下
<el-checkbox-group
v-model="checked"
v-show="!hasNoMatch && data.length > 0"
:class="{ 'is-filterable': filterable }"
class="el-transfer-panel__list"
<el-checkbox
class="el-transfer-panel__item"
:label="item[keyProp]"
:disabled="item[disabledProp]"
:key="item[keyProp]"
v-for="item in filteredData">
<option-content :option="item"></option-content>
</el-checkbox>
</el-checkbox-group>
- 修改为:
<el-checkbox-group
v-model="checked"
v-show="!hasNoMatch && data.length > 0"
:class="{ 'is-filterable': filterable }"
class="el-transfer-panel__list">
<virtual-list
v-if="virtualScroll"
style="height:100%;overflow-y: auto;"
:data-key="keyProp"
:data-sources="filteredData"
:data-component="itemComponent"
:extra-props="virtualListProps"
/>
<template v-else>
<el-checkbox
class="el-transfer-panel__item"
:label="item[keyProp]"
:disabled="item[disabledProp]"
:key="item[keyProp]"
v-for="item in filteredData">
<option-content :option="item"></option-content>
</el-checkbox>
</template>
</el-checkbox-group>
- 在
main.vue
中接受一个prop->virtualScroll
props:{
virtualScroll: {
type: Boolean,
default: false
}
}
- 在业务代码中使用这个transfer时,得传入
:virtual-scroll:true
,代表开启虚拟列表功能
<newTransfer v-model="value" :data="data" :virtual-scroll="true"></newTransfer>
至此,应该是没问题的了,渲染十万条都是没问题的。
本文来自博客园,作者:RHCHIK,转载请注明原文链接:https://www.cnblogs.com/suihung/p/19119131