当前位置: 首页 > 后端技术 > Node.js

当后端一次性丢给你10万条数据时,作为前端工程师,你是如何处理的?

时间:2023-04-03 20:39:50 Node.js

前段时间有朋友问我他??们公司遇到的一个问题,说是后端因为某些原因没有实现分页功能,所以一次返回了20000条数据,前端使用select组件来展示在用户界面中。我听完,顿时明白了他的困惑。如果直接硬编码将20000条数据渲染到select中,肯定会卡死。后来他还说要支持搜索,也是前端实现的。立马产生了兴趣当时想到的方案大致是这样:懒加载+分页(前端维护懒加载数据分布和分页)使用虚拟滚动技术(目前react的antd4。用于优化长列表,类似于表格的分页功能,具体思路是用户只加载每次能看到的数据,滚动到底部的时候再加载下一页的数据,也可以利用虚拟滚动技术进行优化longlists.List,核心思想是每次只渲染可见区域的列表个数,滚动后动态添加元素,通过toppadding撑起整个滚动内容,实现思路也很简单,通过上面的分析,其实可以解决小伙伴们的问题,但是作为最有抱负的前端工程师,笔者仔细梳理了一下,在第一种解决方案的基础上,抽象出一个实际问题:如何渲染一个大的数据列表并支持搜索功能?笔者将模拟不同层次的前端工程师来探究这个问题的价值。希望对你有所启发,学会深入思考。本文作者将从不同经验的程序员的技术角度来分析以上问题,然后开始我们的表演。在开始编写代码之前,让我们先做一些基本的准备工作。笔者首先使用nodejs搭建了一个数据服务器,提供基本的数据请求。核心代码如下:app.use(async(ctx,next)=>{if(ctx.url==='/api/getMock'){letlist=[]//生成指定数量的随机字符串functiongenerateRandomWords(n){letwords='abcdefghijklmnopqrstuvwxyz你很好um气短前端后端设计产品网络但考虑付款分类开发商李开复吉林省丰江大理实德石峰分手快乐',len=words.length,ret=''for(leti=0;ires.json()).then(res=>{if(res.state){data=res.datasetList(data)}})渲染页面{list.map((item,i)=>{return{item.title}{item.name}

{item.text}
})}搜索数据consthandleSearch=(v)=>{letsearchData=data.filter((item,i)=>{returnitem.title.indexOf(v)>-1})setList(searchData)}这样做基本可以满足基本需求,但是缺点也很明显,就是数据是一次性渲染到页面的,数据量巨大会导致以极低的页面性能,导致页面冻结。一个中级工程师的解决方案作为一个有经验的前端开发工程师,肯定对页面性能有所了解,所以对防抖功能和节流功能一定很熟悉,并且用过懒加载、分页之类的东西。看看中层工程师的方案:通过这个流程的优化,代码基本可以用了。下面介绍一下具体的实现方案:懒加载+分页方案懒加载的实现主要是通过监听窗口的滚动。在占位符元素可见后,加载下一个数据。原理是这样的:这里我们通过监听窗口的scroll事件,在poll元素上使用getBoundingClientRect来获取poll元素相对于可见窗口的距离,从而实现自己的懒加载方案。在滚动的过程中,我们还需要注意一个问题,就是当用户回滚的时候,其实是不需要做任何处理的,所以需要加一个单向锁。具体代码如下:functionscrollAndLoading(){if(window.scrollY>prevY)??{//判断用户是否向下滚动prevY=window.scrollYif(poll.current.getBoundingClientRect().top<=window.innerHeight){//请求下一页数据}}}useEffect(()=>{//一些代码constgetData=debounce(scrollAndLoading,300)window.addEventListener('scroll',getData,false)return()=>{window.removeEventListener('scroll',getData,false)}},[])其中prevY存放的是窗口最后一次滚动的距离,只有向下滚动和滚动高度其值为仅在大于上次时更新。至于分页的逻辑,用原生javascript实现分页也很简单。我们定义了几个维度:curPage当前页码pageSize每页显示的数据条数和传入的数据量。具备几个条件,就可以完成我们基本的分页功能了。前端分页核心代码如下:letdata=[];letcurPage=1;letpageSize=16;letprevY=0;//其他代码...functionscrollAndLoading(){if(window.scrollY>prevY)??{//判断用户是否向下滚动prevY=window.scrollYif(poll.current.getBoundingClientRect().top<=window.innerHeight){curPage++setList(searchData.slice(0,pageSize*curPage))}}}Debouncefunction实现了debounce函数,因为比较简单,这里给出简单的debounce函数代码:functiondebounce(fn,time){returnfunction(args){letthat=thisclearTimeout(fn.tid)fn.tid=setTimeout(()=>{fn.call(that,args)},时间);}}实现搜索功能的代码如下:consthandleSearch=(v)=>{curPage=1;上一个=0;searchData=data.filter((它em,i)=>{//使用regex进行匹配,后期支持前端模糊搜索letreg=newRegExp(v,'gi')returnreg.test(item.title)})setList(searchData.slice(0,pageSize*curPage))}需要结合分页实现,所以为了不影响源数据,我们使用临时数据searchData来存储。效果如下:搜索后:搜索前后都使用了懒加载,再也不用担心数据量大造成的性能瓶颈了~高级工程师的解决方案作为一个久经沙场的程序员,我们应该考虑更优雅的实现方式,比如组件化、算法优化、多线程等。就像我们问题中的大数据渲染一样,我们也可以使用虚拟长列表来更优雅简洁的解决我们的需求。至于虚拟长列表的实现,前面已经讲过了,这里就不详细介绍了。对于更大的数据量,比如100万(虽然在实际开发中不会遇到这种无脑场景),我们应该怎么处理呢?第一点,我们可以使用js缓冲区对100万条数据进行分片处理。代码如下:functionmultistep(steps,args,callback){vartasks=steps.concat();setTimeout(function(){vartask=tasks.shift();task.apply(null,args||[]);//调用Apply的参数必须是一个数组if(tasks.length>0){setTimeout(arguments.callee,25);}else{callback();}},25);}这样大量计算导致的js进程可以比较有阻塞的问题。更多性能优化方案可以参考笔者之前的文章。我们也可以在前端需要大量计算的逻辑中使用webworker来移动,保证js主进程的快速响应,让webworker线程在后台进行计算,计算完成后,通过webworker的通信机制通知主进程,比如比如模糊搜索等等,我们可以进一步优化搜索算法,比如二分法等等,所以这些都是高级工程师应该考虑的问题。但是一定要分清场景,找到性价比更高的方案。最后,如果你想了解更多前端技能、实战和学习路线,欢迎在《趣谈前端》专栏学习讨论,一起探索前端的边界。