后端一次给你 10 万条数据,如何优雅展示,到底考察我什么?

更新日期: 2021-12-28阅读: 1.1k标签: 渲染

前言

如果后端真的返回给前端10万条数据,咱们前端要怎么优雅地展示出来呢?(哈哈假设后端真的能传10万条数据到前端)

前置工作

先把前置工作给做好,后面才能进行测试

后端搭建

新建一个server.js文件,简单起个服务,并返回给前端10w条数据,并通过nodemon server.js开启服务

没有安装nodemon的同学可以先全局安装npm i nodemon -g
// server.js

const http = require('http')
const port = 8000;

http.createServer(function (req, res) {
  // 开启Cors
  res.writeHead(200, {
    //设置允许跨域的域名,也可设置*允许所有域名
    'Access-Control-Allow-Origin': '*',
    //跨域允许的请求方法,也可设置*允许所有方法
    "Access-Control-Allow-Methods": "DELETE,PUT,POST,GET,OPTIONS",
    //允许的header类型
    'Access-Control-Allow-Headers': 'Content-Type'
  })
  let list = []
  let num = 0

  // 生成10万条数据的list
  for (let i = 0; i < 100000; i++) {
    num++
    list.push({
      src: 'https://p3-passport.byteacctimg.com/img/user-avatar/d71c38d1682c543b33f8d716b3b734ca~300x300.image',
      text: `我是${num}号嘉宾林三心`,
      tid: num
    })
  }
  res.end(JSON.stringify(list));
}).listen(port, function () {
  console.log('server is listening on port ' + port);
})

前端页面

先新建一个index.html

// index.html

// 样式
<style>
    * {
      padding: 0;
      margin: 0;
    }
    #container {
      height: 100vh;
      overflow: auto;
    }
    .sunshine {
      display: flex;
      padding: 10px;
    }
    img {
      width: 150px;
      height: 150px;
    }
  </style>

// html部分
<body>
  <div id="container">
  </div>
  <script src="./index.js"></script>
</body>

然后新建一个index.js文件,封装一个AJAX函数,用来请求这10w条数据

// index.js

// 请求函数
const getList = () => {
    return new Promise((resolve, reject) => {
        //步骤一:创建异步对象
        var ajax = new XMLHttpRequest();
        //步骤二:设置请求的url参数,参数一是请求的类型,参数二是请求的url,可以带参数
        ajax.open('get', 'http://127.0.0.1:8000');
        //步骤三:发送请求
        ajax.send();
        //步骤四:注册事件 onreadystatechange 状态改变就会调用
        ajax.onreadystatechange = function () {
            if (ajax.readyState == 4 && ajax.status == 200) {
                //步骤五 如果能够进到这个判断 说明 数据 完美的回来了,并且请求的页面是存在的
                resolve(JSON.parse(ajax.responseText))
            }
        }
    })
}

// 获取container对象
const container = document.getElementById('container')

直接渲染

最直接的方式就是直接渲染出来,但是这样的做法肯定是不可取的,因为一次性渲染出10w个节点,是非常耗时间的,咱们可以来看一下耗时,差不多要消耗12秒,非常消耗时间

const renderList = async () => {
    console.time('列表时间')
    const list = await getList()
    list.forEach(item => {
        const div = document.createElement('div')
        div.className = 'sunshine'
        div.innerHTML = `<img src="${item.src}" /><span>${item.text}</span>`
        container.appendChild(div)
    })
    console.timeEnd('列表时间')
}
renderList()

setTimeout分页渲染

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

const renderList = async () => {
    console.time('列表时间')
    const list = await getList()
    console.log(list)
    const total = list.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 = list[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)
    console.timeEnd('列表时间')
}

requestAnimationFrame

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

const renderList = async () => {
    console.time('列表时间')
    const list = await getList()
    console.log(list)
    const total = list.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 = list[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)
    console.timeEnd('列表时间')
}

文档碎片 + requestAnimationFrame

文档碎片的好处

  • 1、之前都是每次创建一个div标签就appendChild一次,但是有了文档碎片可以先把1页的div标签先放进文档碎片中,然后一次性appendChild到container中,这样减少了appendChild的次数,极大提高了性能
  • 2、页面只会渲染文档碎片包裹着的元素,而不会渲染文档碎片
const renderList = async () => {
    console.time('列表时间')
    const list = await getList()
    console.log(list)
    const total = list.length
    const page = 0
    const limit = 200
    const totalPage = Math.ceil(total / limit)

    const render = (page) => {
        if (page >= totalPage) return
        requestAnimationFrame(() => {
            // 创建一个文档碎片
            const fragment = document.createDocumentFragment()
            for (let i = page * limit; i < page * limit + limit; i++) {
                const item = list[i]
                const div = document.createElement('div')
                div.className = 'sunshine'
                div.innerHTML = `<img src="${item.src}" /><span>${item.text}</span>`
                // 先塞进文档碎片
                fragment.appendChild(div)
            }
            // 一次性appendChild
            container.appendChild(fragment)
            render(page + 1)
        })
    }
    render(page)
    console.timeEnd('列表时间')
}

懒加载

为了比较通俗的讲解,咱们启动一个vue前端项目,后端服务还是开着

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

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

IntersectionObserver 性能更好,但是我这里就拿getBoundingClientRect来举例

<script setup lang="ts">
import { onMounted, ref, computed } from 'vue'
const getList = () => {
  // 跟上面一样的代码
}

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) {
    // blank出现在视图,则当前页数加1
    page.value++
  }
}

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

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

虚拟列表

虚拟列表需要讲解的比较多,在这里分享一篇虚拟列表的文章

《结合“康熙选秀”,给大家讲讲“虚拟列表”》(https://juejin.cn/post/6966179727329460232)

链接: https://fly63.com/article/detial/10976

vue中数据更新变化,而页面视图未渲染的解决方案

在使用vue的时候,我们都知道它是双向数据绑定的,但是在使用不熟的情况下,经常会遇到:data中的数据变化了,但是并没有触发页面渲染。下面就整理一些出现这种情况的场景以及解决办法。

服务端渲染和客户端渲染的对比

这里结合art-template模板引擎说明。首先了解下前端页面中如何使用art-template。当不需要对SEO友好的时候,推荐使用客户端渲染;当需要对 SEO友好的时候,推荐使用服务器端渲染

解决使用vue.js未渲染前代码显示问题

在使用vue的时候,偶然发现多次刷新或者网络加载缓慢的时候,会一瞬间出现设置的模板的情况。实在很影响美观,可以使用vue现成的指令来解决这个问题:v-cloak

在微信小程序中渲染html内容的实现

大部分Web应用的富文本内容都是以HTML字符串的形式存储的,通过HTML文档去展示HTML内容自然没有问题。但是,在微信小程序(下文简称为「小程序」)中,应当如何渲染这部分内容呢?

原来 CSS 与 JS 是这样阻塞 DOM 解析和渲染的

估计大家都听过,尽量将 CSS 放头部,JS 放底部,这样可以提高页面的性能。然而,为什么呢?大家有考虑过么?很长一段时间,我都是知其然而不知其所以然,强行背下来应付考核当然可以,但实际应用中必然一塌糊涂

Vue渲染数据理解以及Vue指令

原生JS改变页面数据,必须要获取页面节点,也即是进行DOM操作,jQuery之类的框架只是简化DOM操作的写法,实质并没有改变操作页面数据的底层原理,DOM操作影响性能(导致浏览器的重绘和回流),Vue是一个mvvm框架(库),大幅度减少了DOM操作

Web渲染那些事儿

在决定渲染方式时,需要测量和理解真正的瓶颈在哪里。静态渲染或服务器渲染在多数情况都比较适用,尤其是可交互性对JS依赖较低的场景。下面是一张便捷的信息图,显示了服务器到客户端的技术频谱:

vue从后台获取数据赋值给data,如何渲染更细视图

如果从服务端返回的数据量较少,或者只有几个字段,可以用vue的set方法,如果数据量较大,请直接看第二种情况。官网API是这样介绍的:Vue.set(target,key,value)

react 异步加载数据时的渲染问题

当数据需要异步加载时render获取不到数据可能会报一些错误,此时需要在render函数中加一个判断.行到render时,state对象的haveData为false, 所以此时页面展示 loading,当异步获取数据成功时

Vue.js中v-html渲染的dom添加scoped的样式

在vue.js中,要将一段字符串渲染成html,可以使用v-html指令。但是 官方文档 中的v-html部分也提醒了

点击更多...

内容以共享、参考、研究为目的,不存在任何商业目的。其版权属原作者所有,如有侵权或违规,请与小编联系!情况属实本人将予以删除!