对于较长的列表,比如1000个数组的数据结构,如果想要同时渲染这1000个数据,生成相应的1000个原生dom,我们知道原生的dom元素是很复杂的,如果长列表通过生成如此多的dom元素来实现,很可能使网页失去响应。

  贯穿React核心的就是"virtual dom",我们同样的可以通过用虚拟列表的方式来优雅的优化长列表

  • 原生dom渲染长列表的缺陷
  • 虚拟列表优化长列表的原理
  • 通过react-virtualized来优化长列表
  • 通过react-tiny-virtual-list来优化长列表

本文的原文地址发布在我的博客中:

github.com/fortheallli…

欢迎star和fork~

本文的用例的代码地址为:

github.com/fortheallli…


一、原生dom渲染长列表的缺陷

  首先我们尝试在React项目中,未做任何优化一次性渲染1000个dom,每个dom包含一个img标签,原生dom本身是很复杂的对象,加上img标签后。渲染的效果如下图所示:

  可以很明显的看到白屏的时间很长,因为在React中,不做任何优化,直接渲染这么包含1000个图片的dom节点,即使React本身用了虚拟dom,但是在首次渲染的时候,是实实在在的生成了1000个真实的dom,我们可以查看网页中的真实dom情况,如下所示:

  从上图我们可以看出,是确确实实的生成了1000个真实的dom,进入页面后,需要渲染这1000个dom,因此白屏的时间很长。

  此外,在直接渲染1000个dom节点的页面,触发滚动事件,也会使得内存用量增加,具体可以如下图所示:

此外同时渲染很多dom节点,也会造成一下几个问题:

  • 容易失帧,因为渲染很慢,所以无法维持浏览器的帧率,主观上会显得页面卡顿

  • 网页失去响应,事件等无法及时被触发

  上述的效果都是在PC端展示的,对于特定的移动设备,直接无优化的渲染长列表所造成的问题会更加的放大。长列表的渲染在移动端的很多场景会遇到,比如微博,feeds流中等等。合理的优化长列表,可以提升用户体验。

二、虚拟列表优化长列表的原理

优化长列表的原理很简单,基本原理可以一句话概括:

用数组保存所有列表元素的位置,只渲染可视区内的列表元素,当可视区滚动时,根据滚动的offset大小以及所有列表元素的位置,计算在可视区应该渲染哪些元素。

具体实现步骤如下所示:

  1. 首先确定长列表所在父元素的大小,父元素的大小决定了可视区的宽和高
  2. 确定长列表每一个列表元素的宽和高,同时初始的条件下计算好长列表每一个元素相对于父元素的位置,并用一个数组来保存所有列表元素的位置信息
  3. 首次渲染时,只展示相对于父元素可视区内的子列表元素,在滚动时,根据父元素的滚动的offset重新计算应该在可视区内的子列表元素。这样保证了无论如何滚动,真实渲染出的dom节点只有可视区内的列表元素。
  4. 假设可视区内能展示5个子列表元素,及时长列表总共有1000个元素,但是每时每刻,真实渲染出来的dom节点只有5个。 5.补充说明,这种情况下,父元素一般使用position:relative,子元素的定位一般使用:position:absolute或sticky

通过虚拟列表优化后,同样的显示1000个包含图片的dom,白屏时间会大大的减少。具体效果如下图所示:

对于比无优化的情况,优化后的虚拟列表渲染速度提升很明显。

三、通过react-virtualized来优化长列表

社区实现虚拟列表的React组件很多,较为常用的是react-virtualized和react-tiny-virtual-list,前一个较为全面,第二个较为轻量。接下来会分别来介绍这俩个React组件库。

1、react-virtualized简介

react-virtualized是一个实现虚拟列表较为优秀的组件库,react-virtualized提供了一些基础组件用于实现虚拟列表,虚拟网格,虚拟表格等等,它们都可以减小不必要的dom渲染。此外还提供了几个高阶组件,可以实现动态子元素高度,以及自动填充可视区等等。

react-virtualized的基础组件包含:

  • Grid:用于优化构建任意网状的结构,传入一个二维的数组,渲染出类似棋盘的结构。
  • List:List是基于Grid来实现的,但是是一个维的列表,而不是网状。
  • Table:Table也是基于Grid来实现,表格具有固定的头部,并且可以在垂直方向上滚动
  • Masonry:同样可以在水平方向,也可以在垂直方向滚动,不同于Grid的是可以自定义每个元素的大小,或者子元素的大小也可以是动态变化的
  • Collection:类似于瀑布流的形式,同样可以水平和垂直方向滚动。

值得注意的是这些基础组件都是继承于React中的PureComponent,因此当state变化的时候,只会做一个浅比较来确定重新渲染与否 。

除了这几个基础组件外,react-virtualized还提供了几个高阶组件,比如ArrowKeyStepper 、AutoSizer、CellMeasurer、InfiniteLoader等,本文具体介绍常用的AutoSizer、CellMeasurer和InfiniteLoader。

  • AutoSizer:使用于一个子元素的情况,通过AutoSizer包含的子元素会根据父元素Resize的变化,自动调节该子元素的可视区的宽度和高度,同时调节的还有该子元素可视区真实渲染的dom元素的数目。
  • CellMeasurer:这个高阶组件可以动态的改变子元素的高度,适用于提前不知道长列表中每一个子元素高度的情况。
  • InfiniteLoader:这个高阶组件用于Table或者List的无限滚动,适用于滚动时异步请求等情况

2、react-virtualized基础组件的使用

下面我们来介绍一下常用的基础组件Grid、List。

(1)Grid

所有基础组件基本上都是基于Grid构成的,一个简单的Grid的例子如下:

import { Grid } from 'react-virtualized';
const list = [['Jony yu', 'Software Engineer', 'Shenzhen', 'CHINA', 'GUANGZHOU'],['Jony yu', 'Software Engineer', 'Shenzhen', 'CHINA', 'GUANGZHOU'],['Jony yu', 'Software Engineer', 'Shenzhen', 'CHINA', 'GUANGZHOU'],['Jony yu', 'Software Engineer', 'Shenzhen', 'CHINA', 'GUANGZHOU'],['Jony yu', 'Software Engineer', 'Shenzhen', 'CHINA', 'GUANGZHOU'],['Jony yu', 'Software Engineer', 'Shenzhen', 'CHINA', 'GUANGZHOU']
];function cellRenderer ({ columnIndex, key, rowIndex, style }) {return (<divkey={key}style={style}>{list[rowIndex][columnIndex]}</div>)
}
render(<GridcellRenderer={cellRenderer}columnCount={list[0].length}columnWidth={100}height={300}rowCount={list.length}rowHeight={80}width={300}/>,rootEl
);
复制代码

显示的效果如下图所示:

渲染网格也是只渲染可视区的dom节点,有个有趣的现象是滚动条的大小,这里Grid做了一个细节优化,只有滚动的时候才会显示滚动条,停止滚动后会隐藏滚动条。

(2)List

接着来举例说明一下List的使用:

import { List } from 'react-virtualized';
import  loremIpsum from "lorem-ipsum"
const rowCount = 1000;
const list = Array(rowCount).fill().map(()=>{return loremIpsum({count: 1,units: 'sentences',sentenceLowerBound: 3,sentenceUpperBound: 3}
})
function rowRenderer ({key,         index,      isScrolling, isVisible,   style
}) {return (<divkey={key}style={style}>{list[index]}</div>)
}
export default class TestList extends Component{render(){return <div style={{height:"300px",width:"200px"}}><Listwidth={300}height={300}rowCount={list.length}rowHeight={20}rowRenderer={rowRenderer}/></div>}
}
复制代码

List的使用方法也是极简,指定列表总条数rowCount,每一条的高度rowHeight以及每次渲染的函数rowRenderer,就可以构建一个渲染列表。具体的效果如下图所示:

2、react-virtualized高阶组件的使用

结合List来看看react-virtualized高阶组件的使用。

(1)AutoSizer

首先来看使用不使用AutoSizer的缺点,如下图所示,List只能指定固定的大小,如果其所在的父元素的大小resize了,那么List是不会主动填满父元素的可视区的:

从上图可以看出来,List是无法自动填充父元素的。因此我们这里需要使用AutoSizer。AutoSizer的使用也很简单,我们只需要在List的基础上:

class TestList extends Component{render(){return <div><AutoSizer>{({ height, width }) => (<Listheight={height}rowCount={list.length}rowHeight={20}rowRenderer={rowRenderer}width={width}/>)}</AutoSizer></div>}
}
复制代码

效果如下图所示:

上述可以看出来增加了AutoSizer可以动态的适应父元素宽度和高度的变化。

但是也存在一个问题:

子元素太长,换行后改变了子元素的高度后无法子适应,也就是说仅仅通过基础的组件List是不支持子元素的高度动态改变的场景

(2)CellMeasurer

为了解决上述的子元素可以动态变化的问题,我们可以利用高阶组件CellMeasurer:

import { List,AutoSizer,CellMeasurer, CellMeasurerCache} from 'react-virtualized';
const cache = new CellMeasurerCache({ defaultHeight: 30,fixedWidth: true});
function cellRenderer ({ index, key, parent, style }) {console.log(index)return (<CellMeasurercache={cache}columnIndex={0}key={key}parent={parent}rowIndex={index}><divstyle={style}>{list[index]}</div></CellMeasurer>);
}
复制代码

对于需要渲染的List,如下所示:

class TestList extends Component{render(){return <div><AutoSizer>{({ height, width }) => (<Listheight={height}rowCount={list.length}rowHeight={cache.rowHeight}deferredMeasurementCache={cache}rowRenderer={cellRenderer}width={width}/>)}</AutoSizer></div>}
}
复制代码

最后的结果如下所示:

上图我们看出来,子列表元素的高度可以动态变化,通过CellMeasurer可以实现子元素的动态高度。

(3)InfiniteLoader

最后我们来考虑这种无限滚动的场景,很多情况下我们可能需要分页加载,就是常见的在可视区内无限滚动的场景。react-virtualized提供了一个高阶组件InfiniteLoader用于实现无限滚动。

InfiniteLoader的使用很简单,只要按着文档来即可,就是分页的去在家下一页,滚动分页所调用的函数为:

function loadMoreRows ({ startIndex, stopIndex }) {return new Promise(function(resolve,reject){resolve()}).then(function(){//模拟ajax请求let temList = Array(10).fill(1).map(()=>{return loremIpsum({count: 1,units: 'sentences',sentenceLowerBound:3,sentenceUpperBound:3})})list = list.concat(temList)})
}
复制代码

最后的效果如下:

看起来跟基础组件List一样,其实唯一的区别就是会在滚动的时候自动执行loadMoreRows函数去更新list

(4)总结

通过基础组件Grid、List以及高阶组件AutoSizer、CellMeasurer和InfiniteLoader,已经可以构建出比较复杂的场景,但是有一个缺陷,就是CellMeasurer虽说一定程度上支持动态子元素的高度的变化,其实是一种估算,存在很多边界情况,无法适应于动态元素的场景,特别是文本节点较多导致的高度变化。但是对于图片节点的动态高度支持没有很大的问题。

举例一种边界情况,CellMeasurer无法支持文本动态高度的情况:

从上图可以看到,慢慢缩小的过程中,如果缩的太小,并没有动态的撑大子元素的高度,出现了文字的重叠。

四、通过react-tiny-virtual-list来优化长列表

react-tiny-virtual-list是一个较为轻量的实现虚拟列表的组件,不同于react-virtualized支持网格以及表格等渲染优化。 react-tiny-virtual-list只支持列表,使用方便,其源码也只有700多行。

使用极其简单:

import VirtualList from 'react-tiny-virtual-list';
const data = ['A', 'B', 'C', 'D', 'E', 'F','A', 'B', 'C',
'D', 'E', 'F','A', 'B', 'C', 'D', 'E', 'F',
'A', 'B', 'C', 'D', 'E', 'F'];
export default class TinyVirtual extends Component {render(){return <VirtualListwidth='100%'height={200}itemCount={data.length}itemSize={50} // Also supports variable heights (array or function getter)renderItem={({index, style}) =><div key={index} style={style}>// The style property contains the item's absolute position Letter: {data[index]}, Row: #{index}</div>}/>}
}
复制代码

最后的渲染结果也是相似的,也可以支持无限滚动等等。

但是react-tiny-virtual-list有一个致命的缺点:

完全不支持子元素的动态高度或者宽度

五、总结

本文介绍了虚拟列表的优化的原理,以及常用的React可以优化虚拟列表的组件库。在接下来的文章中,会具体的介绍react-tiny-virtual-list和react-virtualized的源码,敬请期待。

在React项目中,如何优雅的优化长列表相关推荐

  1. 如何优雅地在React项目中使用Redux

    前言 或许你当前的项目还没有到应用Redux的程度,但提前了解一下也没有坏处,本文不会安利大家使用Redux 概念 首先我们会用到哪些框架和工具呢? React UI框架 Redux 状态管理工具,与 ...

  2. 优雅的在React项目中使用Redux

    概念 首先我们会用到哪些框架和工具呢? React UI框架 Redux 状态管理工具,与React没有任何关系,其他UI框架也可以使用Redux react-redux React插件,作用:方便在 ...

  3. 优雅的在React项目中使用Redux 1

    或许你当前的项目还没有到应用Redux的程度,但提前了解一下也没有坏处 首先我们会用到哪些框架和工具呢? React UI框架 Redux 状态管理工具,与React没有任何关系,其他UI框架也可以使 ...

  4. 数据可视化:在 React 项目中使用 Vega 图表 (一)

    相关包 打开搜索引擎,一搜 Vega,发现相关的包有好几个,Vega, Vega-Lite, Vega-Embed,React-Vega 等等,不免让人头晕. 别急,它们之间的关系三四句话就能说明白, ...

  5. React 项目中使用Echarts

    直接上代码吧: react 项目中添加Echarts 相关模块 npm install echarts --save 代码: import React from 'react' import * as ...

  6. react项目中使用mocha结合chai断言库进行单元测试

    react项目中使用mocha结合chai断言库进行单元测试 git地址:https://github.com/yancekang/... 如果对你有所帮助,感谢start 项目搭建 create-r ...

  7. python导入标准库对象的语句_Python项目中如何优雅的import

    Python项目中如何优雅的import 前言 之前有一篇关于Python编码规范的随笔, 但是写的比较杂乱, 因为提到了import语句, 在篇文章中, 我专门来讲Python项目中如何更好的imp ...

  8. 中使用js修改变量值_谈一谈css-in-js在React项目中的使用

    一.什么是css-in-js 参考:[css in js 简介] 简单来说,传统的前端方案推崇"关注点分离"原则,HTML.CSS.JavaScript 应该各司其职,进行分离. ...

  9. [react] 在React项目中你用过哪些动画的包?

    [react] 在React项目中你用过哪些动画的包? react-transition-group 个人简介 我是歌谣,欢迎和大家一起交流前后端知识.放弃很容易, 但坚持一定很酷.欢迎大家一起讨论 ...

  10. react 使用 leaflet 百度地图_【React】react项目中应用百度地图添加起始点绘制路线...

    如图:项目中百度地图的应用添加起始点.终点并绘制路线 在展示代码的时候首先展示一下后台返回给我的接口 { 其中position_list参数代表的是用户的行驶点, area参数代表的是服务区的坐标点, ...

最新文章

  1. fgbio,picard处理带有UMI的fq序列
  2. 脑电分析系列[MNE-Python-12]| 注释连续数据
  3. citrix 路径遍历 rce漏洞(CVE-2019-19781)
  4. oracle 定时任务 job 调用存储过程有回到输出参数(含out参数)
  5. 《犯罪心理学》读书笔记(part1)--蔑视社会秩序的最明显、最极端的表现就是犯罪
  6. 网红品牌,都是“营销狗”?
  7. synchronized方法与synchronized代码块的区别
  8. 判断二叉树是否对称的代码
  9. php如何实现qq第三方登录,PHP实现qq第三方登录
  10. python size和count_groupby 的妙用(注意size和count)
  11. python金融网课_Python金融数据分析
  12. SEO优化:站群的操作方法有哪些,网站集群系统是什么?
  13. 常用计算器就是计算机吗,计算器和计算机的区别?
  14. Ionic开发框架的安装及Ionic项目的创建
  15. 计算机与网络时间同步,电脑时间同步,教您怎么让电脑时间和网络时间同步
  16. php选课删除选课成绩统计,高校选修课平时成绩管理系统
  17. ips细胞再生视网膜研究进展
  18. 互联网晚报 | 周杰伦起诉网易不正当竞争案庭审结束;特斯拉中国工厂普通工人月薪1万块;SpaceX“星舰”发射计划推迟...
  19. 机器学习常用「微积分」知识速查手册
  20. 「真香系列」新物种首发亮相 聚划算爆款孵化玩法升级

热门文章

  1. [BZOJ4810][Ynoi2017]由乃的玉米田 莫队+bitset
  2. jquery操作元素
  3. 【读书笔记】钢铁是怎么炼成的
  4. 通过sql server的作业调度+存储过程来实现系统定时任务的方法
  5. matlab中ezplot和plot, fplot这3
  6. Mac 连不上华为 p9 处理历程(二)
  7. 苹果移动设备用什么管理比较好?有什么推荐?
  8. JavaScript静态页面值传递之URL篇
  9. 函数对象、对象、原型
  10. vue2.0实现银行卡类型种类的选择