什么是虚拟列表?
根据滚动容器的可视区域渲染长列表的某一个部分数据的技术,即长列表的按需渲染
普通长列表的性能消耗
首先创建一个普通长列表,包含5000项
import React from 'react'
export default class App extends React.Component {
constructor (props) {
super(props)
this.state = {
complexDOMs: []
}
this.onCreateComplexDOMs = this.onCreateComplexDOMs.bind(this)
}
onCreateComplexDOMs () {
this.setState({
complexDOMs: Array.from(Array(5000).keys())
});
}
render () {
const {complexDOMs}=this.state;
return (
<div style={{ marginLeft: '10px' }}>
<h3>Creat large of DOMs:</h3>
<button onClick={this.onCreateComplexDOMs}>Create Complex DOMs</button>
<div>{complexDOMs.map((item) => {
return (
<div key={item}>
<p>#${item} eligendi voluptatem quisquam</p>
<p>
Modi autem fugiat maiores. Doloremque est sed quis qui
nobis. Accusamus dolorem aspernatur sed rem.
</p>
</div>
);
})}</div>
</div>
)
}
}import React from 'react'
function createMarkup (doms) {
return doms.length ? { __html: doms.join(' ') } : { __html: '' }
}
export default class App extends React.Component {
constructor (props) {
super(props)
this.state = {
complexDOMs: []
}
this.onCreateComplexDOMs = this.onCreateComplexDOMs.bind(this)
}
onCreateComplexDOMs () {
const array = []
for (var i = 0; i < 5000; i++) {
array.push(`
<div class='list-item'>
<p>#${i} eligendi voluptatem quisquam</p>
<p>Modi autem fugiat maiores. Doloremque est sed quis qui nobis. Accusamus dolorem aspernatur sed rem.</p>
</div>
`)
}
this.setState({
complexDOMs: array
})
}
render () {
return (
<div style={{ marginLeft: '10px' }}>
<h3>Creat large of DOMs:</h3>
<button onClick={this.onCreateComplexDOMs}>Create Complex DOMs</button>
<div dangerouslySetInnerHTML={createMarkup(this.state.complexDOMs)} />
</div>
)
}
}
使用虚拟列表技术展示上述列表
import React from "react";
export default class App extends React.Component {
constructor(props) {
super(props);
this.state = {
complexDOMs: [],
start: 0, // 可见列表的第一项的索引
end: 0 // 可见列表的最后一项的索引
};
this.containerRef = React.createRef();
this.onCreateComplexDOMs = this.onCreateComplexDOMs.bind(this);
}
componentDidMount() {
this.calculateRange();
}
// 全部可容纳的列表项的数量
getOffset = (scrollTop) => {
// 为了简化,便于理解,这里我们假设列表项高度为79
return Math.floor(scrollTop / 79);
};
// 可见范围的列表项数量
getViewCapacity = (containerHeight) => {
// 假设列表项高度为79
return Math.ceil(containerHeight / 79);
};
// 触发scroll时,实时更新展示范围
calculateRange = () => {
const overscan = 10; // 不在显示区域的缓冲值
const ele = this.containerRef.current;
if (ele) {
const offset = this.getOffset(ele.scrollTop); // 容器能容纳的列表项数
const viewCapacity = this.getViewCapacity(ele.clientHeight); // 显示区域能容纳的列表项数
const from = offset - overscan; // 从哪一项开始展示
const to = offset + viewCapacity + overscan; // 从哪一项结束
this.setState({
start: from > 0 ? from : 0,
end: to < 5000 ? to : 5000
});
}
};
onCreateComplexDOMs = (e) => {
e && e.preventDefault();
this.setState({
complexDOMs: Array.from(Array(5000).keys())
});
};
render() {
const { start, end, complexDOMs } = this.state;
return (
<>
<button onClick={this.onCreateComplexDOMs}>add virtualList</button>
<div
className="container"
ref={this.containerRef}
onScroll={this.calculateRange}
style={{ height: "200px", overflow: "auto" }}
>
{/* 预估wrapper高度,修改height改变滚动进度条的长度 */}
{/* 修改margintop留出wrapper到顶部的距离 */}
<div
className="wrapper"
style={{
height: complexDOMs.length * 79 - start * 79,
marginTop: start * 79
}}
>
{/* 展示可见范围的列表项 */}
{complexDOMs.slice(start, end).map((item) => {
return (
<div key={item}>
<p>#${item} eligendi voluptatem quisquam</p>
<p>
Modi autem fugiat maiores. Doloremque est sed quis qui
nobis. Accusamus dolorem aspernatur sed rem.
</p>
</div>
);
})}
</div>
</div>
</>
);
}
}
在使用performace测试一下
只消耗了16.7ms