分片渲染与虚拟列表

分片渲染与虚拟列表

一般情况下,接口返回大量数据时都会进行分页处理,例如表格数据。但是在一些特殊的情况下(例如返回树状结构的数据)后端是没有进行分页处理的,当返回的数据很大时,一次性渲染非常容易造成页面卡顿,卡死的情况。所以说有关高性能,大数据量的列表渲染的示例已经非常常见。

分片渲染

分片渲染:简单的说就是说一个执行完在执行下一个,其思想就是建立一个队列,通过定时器进行渲染

<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
上述每次渲染的个数都是通过计算得来的,所以还需要监听屏幕的变化,正确的获取该值。
:::

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

兢兢业业的码农

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值