我的发!被后端五万条数据爆破我是怎么处理的

前言

今天面试的时候面试官直接问了一句后端一次性返回10万条数据给你,你如何处理? ,我脑中浮现的第一句话就是拿着物理学圣剑找后端进行 "友好的协商",谁打赢了听谁的。不过虽然这种情况很少,不过我在实际开发中还真遇到了类似的情况,接下来我将带着大家一些处理方案。

正文

方案一 直接渲染

如果请求到10万条数据直接渲染,页面会卡死的,很显然,这种方式是不可取的。 pass!

js 复制代码
 async getData() {
      this.loading = true;
      const res = await axios.get("/api/getData");
      this.arr = res.data.data;
      this.loading = false;
}

方案二 setTimeout分页渲染

这个方法就是,把10w按照每页数量limit分成总共Math.ceil(total / limit)页,然后利用setTimeout,每次渲染1页数据,这样的话,渲染出首页数据的时间大大缩减了。

js 复制代码
const renderData = async () => {
    const Data = await getData()
    const total = Data.length
    
    const page = 0
    //每页数量
    const limit = 200
    //总页数
    const totalPage = Math.ceil(total / limit)

    const render = (page) => {
        if (page >= totalPage) return
        setTimeout(() => {
            for (let i = page * limit; i < page * limit + limit; i++) {
                const item = Data[i]
                const div = document.createElement('div')
                div.className = 'sunshine'
                div.innerHTML = `<img src="${item.src}" /><span>${item.text}</span>`
                container.appendChild(div)
            }
            render(page + 1)
        }, 0)
    }
    
    render(page)
}

方案三 requestAnimationFrame

使用requestAnimationFrame代替setTimeout,减少了重排的次数,极大提高了性能,建议大家在渲染方面多使用requestAnimationFrame

js 复制代码
const renderData = async () => {
    const Data = await getData()
    const total = Data.length
    
    const page = 0
    //每页数量
    const limit = 200
    //总页数
    const totalPage = Math.ceil(total / limit)


    const render = (page) => {
        if (page >= totalPage) return
        // 使用requestAnimationFrame代替setTimeout
        requestAnimationFrame(() => {
            for (let i = page * limit; i < page * limit + limit; i++) {
                const item = Data[i]
                const div = document.createElement('div')
                div.className = 'sunshine'
                div.innerHTML = `<img src="${item.src}" /><span>${item.text}</span>`
                container.appendChild(div)
            }
            render(page + 1)
        })
    }
    
    render(page)
}

方案四 表格滚动触底加载

原理很简单,就是在列表尾部放一个空节点,然后先渲染第1页数据,向上滚动,等到空节点出现在视图中,就说明到底了,这时候再加载第二页,往后以此类推。

至于怎么判断blank出现在视图上,可以使用getBoundingClientRect方法获取top属性

js 复制代码
<script setup lang="ts">
import { onMounted, ref, computed } from 'vue'

const container = ref<HTMLElement>() // container节点
const blank = ref<HTMLElement>() // blank节点
const list = ref<any>([]) // 列表
const page = ref(1) // 当前页数
const limit = 200 // 一页展示
// 最大页数
const maxPage = computed(() => Math.ceil(list.value.length / limit))
// 真实展示的列表
const showList = computed(() => list.value.slice(0, page.value * limit))
const handleScroll = () => {
  // 当前页数与最大页数的比较
  if (page.value > maxPage.value) return
  const clientHeight = container.value?.clientHeight
  const blankTop = blank.value?.getBoundingClientRect().top
  if (clientHeight === blankTop) {
    // 出现在视图,则当前页数加1
    page.value++
  }
}

onMounted(async () => {
  const res = await getList()
  list.value = res
})
</script>

<template>
  <div id="container" @scroll="handleScroll" ref="container">
    <div class="sunshine" v-for="(item) in showList" :key="item.tid">
      <img :src="item.src" />
      <span>{{ item.text }}</span>
    </div>
    <div ref="blank"></div>
  </div>
</template>

方案六 虚拟列表

什么是虚拟列表?

所谓的虚拟列表实际上是前端障眼法的一种表现形式。

看到的好像所有的数据都渲染了,实际上只渲染可视区域 的部分罢了。如果10万条数据都渲染,那得需要多少dom节点元素呢?所以我们只给用户看,他当下能看到的如果用户要下拉滚动条或者上拉滚动条再把对应的内容呈现在可视区域内。这样就实现了看着像是所有的dom元素每一条数据都有渲染的障眼法效果了

实现

html 复制代码
<template>
  <!-- 虚拟列表容器,类似"窗口",窗口的高度取决于一次展示几条数据
            比如窗口只能看到10条数据,一条40像素,10条400像素
            故,窗口的高度为400像素,注意要开定位和滚动条 -->
  <div
    class="virtualListWrap"
    ref="virtualListWrap"
    @scroll="handleScroll"
    :style="{ height: itemHeight * count + 'px' }"
  >
    <!-- 占位dom元素,其高度为所有的数据的总高度 -->
    <div
      class="placeholderDom"
      :style="{ height: allListData.length * itemHeight + 'px' }"
    ></div>
    <!-- 内容区,展示10条数据,注意其定位的top值是变化的 -->
    <div class="contentList" :style="{ top: topVal }">
      <!-- 每一条(项)数据 -->
      <div
        v-for="(item, index) in showListData"
        :key="index"
        class="itemClass"
        :style="{ height: itemHeight + 'px' }"
      >
        {{ item.name }}
      </div>
    </div>
    <!-- 加载中部分 -->
    <div class="loadingBox" v-show="loading">
      <i class="el-icon-loading"></i>
      &nbsp;&nbsp;<span>loading...</span>
    </div>
  </div>
</template>
js 复制代码
<script>
import axios from "axios";
export default {
  data() {
    return {
      allListData: [], // 所有的数据,比如这个数组存放了十万条数据
      itemHeight: 40, // 每一条(项)的高度,比如40像素
      count: 10, // 一屏展示几条数据
      start: 0, // 开始位置的索引
      end: 10, // 结束位置的索引
      topVal: 0, // 父元素滚动条滚动,更改子元素对应top定位的值,确保联动
      loading: false,
    };
  },
  computed: {
    // 从所有的数据allListData中截取需要展示的数据showListData
    showListData: function () {
      return this.allListData.slice(this.start, this.end);
    },
  },
  async created() {
    this.loading = true;
    const res = await axios.get("/api/getData");
    this.allListData = res.data.data;
    this.loading = false;
  },
  methods: {
    // 滚动这里可以加上节流,减少触发频次
    handleScroll() {
      const scrollTop = this.$refs.virtualListWrap.scrollTop;
      this.start = Math.floor(scrollTop / this.itemHeight);
      this.end = this.start + this.count;
      this.topVal = this.$refs.virtualListWrap.scrollTop + "px";
    },
  },
};
</script>

结尾

如果你觉得此文对你有帮助的话,点个赞,鼓励一下作者。假如您也和我一样,在准备春招。欢迎加我微信shunwuyu ,这里有几十位一心去大厂的友友可以相互鼓励,分享信息,模拟面试,共读源码,齐刷算法,手撕面经。来吧,友友们!"

相关推荐
昱禹4 分钟前
关于CSS Grid布局
前端·javascript·css
啊QQQQQ12 分钟前
HTML:相关概念以及标签
前端·html
就叫飞六吧1 小时前
vue2和vue3全面对比
前端·javascript·vue.js
Justinc.1 小时前
CSS基础-盒子模型(三)
前端·css
qq_2518364571 小时前
基于ssm vue uniapp实现的爱心小屋公益机构智慧管理系统
前端·vue.js·uni-app
._Ha!n.1 小时前
Vue基础(二)
前端·javascript·vue.js
码农超哥同学2 小时前
Python知识点:在Python编程中,如何使用Gensim进行主题建模
开发语言·python·面试·编程
风清扬_jd2 小时前
Chromium 硬件加速开关c++
java·前端·c++
weixin_545032313 小时前
JavaScript代码如何测试?
开发语言·javascript·ecmascript
谢尔登3 小时前
【React】事件机制
前端·javascript·react.js