背景
无限滚动列表是指当列表滚动到底部时,自动加载更多数据,使无限滚动成为可能。在很多现代的 Web 应用程序中,无限滚动列表已经成为非常流行的 UI 设计风格。
React 是一个非常流行的前端框架,因此,在 React 中实现无限滚动列表是非常重要的知识。
实现
在 React 中,我们通常使用虚拟列表来实现无限滚动列表。这样做的好处是能够优化列表性能,避免加载大量的数据。
简单实现
下面是一个简单的无限滚动列表,使用了 React Hooks。
// www.javascriptcn.com code example
import React, { useState, useEffect } from 'react';
function App() {
const [list, setList] = useState([]);
const [loading, setLoading] = useState(false);
const [page, setPage] = useState(0);
const loadMore = async () => {
try {
setLoading(true);
const newList = await fetch(`https://example.com/api?page=${page}`).then(res => res.json());
setList([...list, ...newList]);
setPage(page + 1);
} catch (err) {
console.error(err);
} finally {
setLoading(false);
}
};
useEffect(() => {
loadMore();
}, []);
const handleScroll = () => {
const scrollBottom = window.innerHeight + window.pageYOffset >= document.body.offsetHeight - 200;
if (scrollBottom && !loading) {
loadMore();
}
};
useEffect(() => {
window.addEventListener('scroll', handleScroll);
return () => window.removeEventListener('scroll', handleScroll);
}, [list]);
return (
<ul>
{list.map(item => (
<li key={item.id}>
<div>{item.title}</div>
<div>{item.body}</div>
</li>
))}
{loading && <div>Loading...</div>}
</ul>
);
}这个简单实现的基本原理是:
- 使用
useState声明当前列表数据和页面状态(是否在加载中); - 使用
useEffect在组件挂载时发起第一次加载; - 监听滚动事件,在滚动到底部时加载更多数据;
- 在列表底部显示加载状态。
高级实现
该方法的问题是,每次加载都会完全填充列表并导致性能问题。
更好的实现是,始终仅渲染列表可见部分。最好的方式是仅在顶部插入新数据或从底部删除旧数据。这使得列表始终保持与 ul 的变化一致,也可以最小化 DOM 操作。
这可以通过使用 React 的虚拟列表库 react-window 和 react-virtualized-auto-sizer 库来实现。
// www.javascriptcn.com code example
import React, { useState, useEffect } from 'react';
import { FixedSizeList as List } from 'react-window';
import AutoSizer from 'react-virtualized-auto-sizer';
function App() {
const [list, setList] = useState([]);
const [loading, setLoading] = useState(false);
const [page, setPage] = useState(0);
const loadMore = async () => {
try {
setLoading(true);
const newList = await fetch(`https://example.com/api?page=${page}`).then(res => res.json());
setList([...list, ...newList]);
setPage(page + 1);
} catch (err) {
console.error(err);
} finally {
setLoading(false);
}
};
useEffect(() => {
loadMore();
}, []);
const Row = ({ index, style }) => {
const item = list[index];
return (
<div style={style}>
<div>{item.title}</div>
<div>{item.body}</div>
</div>
);
};
const itemCount = list.length;
const handleScroll = ({ scrollOffset, scrollDirection }) => {
const threshold = 200;
if (
scrollOffset < threshold &&
!loading &&
scrollDirection === 'backward' &&
page > 0
) {
loadMore();
}
};
return (
<AutoSizer>
{({ height, width }) => (
<List
height={height}
itemCount={itemCount}
itemSize={50}
width={width}>
{Row}
</List>
)}
</AutoSizer>
);
}该实现使用 FixedSizeList 和 AutoSizer,可以自动适应视图大小,并减少不必要的 DOM 操作。
遇到的问题
性能问题
在传统的滚动列表中,当列表数目很大时,会导致性能问题,即长时间等待数据渲染。React 的虚拟列表技术可以避免这个问题,它只渲染当前可见的列表内容,不再任何其他地方渲染 DOM。
但是,实现虚拟列表也存在一些问题。例如,列表的新数据可以在列表的任何地方添加或删除,因此,我们无法使用无效高度的占位符来优化性能。当我们滚动页面时,所有元素的大小仍然需要进行计算。
另外,由于虚拟列表是面向动态注入的,当我们尝试调整元素时,虚拟列表实际上并不知道它的内容以及需要多少空间。GetEnumerator 方法返回一个枚举器,其中包含所有可见项目的索引。还要注意它只能显示区域的上/下元素。在这种情况下,如果我们提取一个区域,它会分配空间以缓存对同一方向的多个请求,从而占用更多的内存。
外观问题
测试中发现,在列表滚动时,页面上出现了一种反弹效果,在往上滑后,项立即出现。我们看到占位符,有大量远程数据而没有本地数据。
为了解决这个问题,需要在页面顶部和底部都加载一些额外的内容,例如顶部用于“加载中”消息和底部占用空间。当用户滚动到接近底部甚至到达底部时,足够的缓冲区将保证初始部分在滑动时不可见。这样,当滚动满足加载新数据的条件时,可以通过添加到底部而无需删除任何项。
小结
在 React 中实现无限滚动列表需要考虑列表性能和外观问题,使用虚拟列表可以解决这个问题。React Hooks 是一种方便快捷的实现方式,而使用 react-window 和 react-virtualized-auto-sizer 库更加高效。通过实现一个完整的无限滚动列表,我们还可以了解到虚拟列表的一些优化技术。
来源:JavaScript中文网 ,转载请注明来源 https://www.javascriptcn.com/post/67d5c527a941bf7134afd1eb