分片渲染与虚拟列表
一般情况下,接口返回大量数据时都会进行分页处理,例如表格数据。但是在一些特殊的情况下(例如返回树状结构的数据)后端是没有进行分页处理的,当返回的数据很大时,一次性渲染非常容易造成页面卡顿,卡死的情况。所以说有关高性能,大数据量的列表渲染的示例已经非常常见。
分片渲染
分片渲染:简单的说就是说一个执行完在执行下一个,其思想就是建立一个队列,通过定时器进行渲染
<template>
<div class="slice_rendering">
<div class="BaseApp-page-main main">
<div class="common-title">分片渲染</div>
<div ref="content" class="content"></div>
</div>
</div>
</template>
<script>
import { getCurrentInstance, onMounted, ref } from "vue-demi";
export default {
setup() {
const internalInstance = getCurrentInstance();
let global = internalInstance.appContext.config.globalProperties;
// 元素组
const dataList = ref([]);
// 分割后的数据
const array = ref([]);
// 父元素的height
const hei = ref(0);
// vue3获取父元素的方法
const content = ref();
// 每次渲染的个数
const num = ref(0);
// 标记
const index = ref(0);
onMounted(() => {
if (content.value) {
// 获取父元素的高度
hei.value = content.value.offsetHeight;
// 获取每次渲染的个数
num.value = Math.ceil(hei.value / 50);
}
getDataList();
});
const getDataList = async () => {
await global.$http
.get(global.$mockUrl.getDataList, {
params: {
pageNo: 1,
},
})
.then((res) => {
dataList.value = res.data;
// 数组分割
array.value = group(dataList.value, num.value);
// 渲染
load();
});
};
const load = () => {
if (index.value <= array.value.length - 1) {
setTimeout(() => {
array.value[index.value].forEach((item) => {
let items = document.createElement("div");
// 添加样式
items.style.width = "100%";
items.style.height = "40px";
items.style.lineHeight = "40px";
items.style.backgroundColor = "#ccc";
items.style.margin = "10px 0";
items.innerHTML = item.name;
content.value.appendChild(items);
});
index.value++;
// 递归
load();
}, 500);
}
};
/**
* 数组拆分
*/
const group = (array, subGroupLength) => {
let index = 0;
let newArray = [];
while (index < array.length) {
newArray.push(array.slice(index, (index += subGroupLength)));
}
return newArray;
};
},
};
</script>
<style lang="scss" scoped>
.slice_rendering {
.main {
width: 100%;
height: 100%;
.content {
width: 100%;
height: calc(100% - 26px);
overflow-y: auto;
}
}
.item {
width: 100%;
height: 40px;
line-height: 40px;
background-color: #ccc;
margin: 10px 0;
}
}
</style>
::: tip
DOM 个数达到多少浏览器会出现卡顿,这个是要看情况的要看什么节点(文本节点比较节省资源,而图片和视频等就比较耗费资源)以及是否有事件绑定在上面,绑定的事件有多少等等。另外还跟浏览器的种类以及用户电脑的配置有关,并不一个简单的数字能够说明。
:::
::: tip
分层渲染比一次渲染的性能更好,但是渲染的 dom 元素的个数并没有发生改变,实际上并不是最优的解决办法
:::
虚拟列表
虚拟列表:实际上是一种实现方案,只是对可视区域进行渲染,对非可视区域中的区域不渲染或只渲染一部分(渲染的部分叫缓冲区,不渲染的部分叫虚拟区),从而达到极高的性能。
<!-- index.vue -->
<template>
<div class="virtual_list">
<div class="BaseApp-page-main main">
<div class="common-title">虚拟列表</div>
<div ref="content" class="content">
<copyVirtualList :num="num" :dataList="dataList"></copyVirtualList>
</div>
</div>
</div>
</template>
<script>
import copyVirtualList from "./copyVirtualList.vue";
import { getCurrentInstance, onMounted, ref } from "vue-demi";
export default {
components: {
copyVirtualList,
},
setup() {
const internalInstance = getCurrentInstance();
let global = internalInstance.appContext.config.globalProperties;
const dataList = ref([]);
const hei = ref(0);
const num = ref(0);
const content = ref();
onMounted(() => {
if (content.value) {
// 获取每次渲染的个数
hei.value = content.value.offsetHeight;
num.value = Math.ceil(hei.value / 50);
}
getDataList();
});
const getDataList = async () => {
await global.$http
.get(global.$mockUrl.getDataList, {
params: {
pageNo: 1,
},
})
.then((res) => {
dataList.value = res.data;
});
};
return {
dataList,
num,
content,
};
},
};
</script>
<style lang="scss" scoped>
.virtual_list {
.main {
width: 100%;
height: 100%;
.content {
width: 100%;
height: calc(100% - 26px);
overflow-y: auto;
.item {
width: 100%;
height: 40px;
line-height: 40px;
background-color: #ccc;
margin: 10px 0;
}
}
}
}
</style>
<!-- copyVirtualList.vue -->
<template>
<div id="VirtualList" ref="virtuallist" @scroll="handleScroll">
<div :style="{ height: dataList.length * 50 + 'px' }"></div>
<div id="container" ref="container" :style="{ top: offsetTop }">
<div v-for="(item, index) in itemList" :key="index" class="item">
{{ item.id }}:{{ item.name }}
</div>
</div>
</div>
</template>
<script>
import { computed, ref, toRefs } from "vue-demi";
export default {
props: {
num: {
type: Number,
default: 0,
},
dataList: {
type: Array,
default: () => [],
},
},
setup(props) {
const { num, dataList } = toRefs(props);
const start = ref(0);
const end = ref(0);
end.value = num.value;
const offsetTop = ref(0);
const virtuallist = ref();
const itemList = computed(() =>
dataList.value.slice(start.value, end.value)
);
const handleScroll = () => {
start.value = virtuallist.value.scrollTop / 50;
end.value = start.value + num.value;
offsetTop.value = virtuallist.value.scrollTop + "px";
};
return {
start,
end,
itemList,
offsetTop,
virtuallist,
handleScroll,
};
},
};
</script>
<style lang="scss" scoped>
#VirtualList {
overflow: auto;
width: 100%;
height: 100%;
position: relative;
}
#container {
width: 100%;
position: absolute;
left: 0;
top: 0;
}
.item {
width: 100%;
height: 40px;
line-height: 40px;
background-color: #ccc;
margin: 10px 0;
}
</style>
:::tip
上述每次渲染的个数都是通过计算得来的,所以还需要监听屏幕的变化,正确的获取该值。
:::