本模块提供基于原生 DOM 操作的高性能虚拟滚动列表工作流,适用于无限上拉/下拉数据列表、聊天记录等功能场景。
npm i @just4/virtual-list
import { VirtualList } from '@just4/virtual-list';
const virtualList = new VirtualList({
// 初始化参数
});
初始化参数说明:
参数名 | 类型 | 说明 |
---|---|---|
container | HTMLElement | 滚动容器(HTML 元素)。 |
itemKey | string | 数据项的 key 属性。 |
maxItemCount | number | 最大渲染的节点数,默认为 100 |
prefetchDistance | number | 预加载距离,为滚动容器高度的倍数,默认为 2。 |
defaultView | 'head' | 'foot' | 默认视图,'head' 表示开头,'foot' 表示末尾。 |
dataSource | Object | 数据源(详见下文说明)。 |
renderer | Object | 渲染器(详见下文说明)。 |
VirtualList
对象会调用数据源去加载所需的数据,包括初始数据、下一页数据和上一页数据。数据源应包含以下异步方法:
loadInitialData()
:加载初始数据。loadNextData(ref)
:加载下一页数据,其中 ref 是当前最后一条数据的 key 值。loadPreviousData(ref)
:加载上一页数据,其中 ref 是当前第一条数据的 key 值。以下为数据源对象的示例:
// 假设 allData 中包含所有数据
const allData = [
// ...
];
// 分页的页大小
const PAGE_SIZE = 50;
const dataSource = {
loadInitialData() {
return new Promise((resolve) => {
// setTimeout 用于模拟数据加载的延迟
setTimeout(function() {
resolve(
{
data: allData.slice(0, PAGE_SIZE),
reachedFootBoundary: false,
reachedHeadBoundary: true
}
);
}, 500);
});
},
loadNextData(ref) {
return new Promise((resolve) => {
setTimeout(function() {
for (let i = 0; i < allData.length; i++) {
if (allData[i].id.toString() === String(ref)) {
resolve(allData.slice(i + 1, i + 1 + PAGE_SIZE));
break;
}
}
resolve([]);
}, 500);
});
},
loadPreviousData(ref) {
return new Promise((resolve) => {
setTimeout(function() {
for (let i = 0; i < allData.length; i++) {
if (allData[i].id.toString() === String(ref)) {
resolve(allData.slice(Math.max(0, i - PAGE_SIZE), i));
break;
}
}
resolve([]);
}, 500);
});
}
};
如果返回的是空数组,则表示数据已到达边界,不会在该方向上继续加载。特别地,如果 loadInitialData
返回的是空数组,则表示当前没有数据,不会再触发 loadNextData
和 loadPreviousData
。
模块内部会把数据传入渲染器,并把渲染器返回的 DOM 节点渲染到容器内,渲染器是一个包含以下方法的对象:
renderItems
:渲染数据项,需返回一个 DOM 节点的类数组或数组;renderLoading
:渲染加载中界面,需返回 DOM 节点;renderError
:渲染加载异常界面,需返回 DOM 节点;renderBoundary
:渲染数据边界界面,需返回 DOM 节点;renderEmpty
:渲染空数据提示,需返回 DOM 节点。其中 renderLoading
、renderError
、renderBoundary
的第一个参数为渲染位置、第二个参数为虚拟滚动列表的实例。渲染位置包括:
RenderPosition.Main
:主位置,指的是没有数据时的状态。RenderPosition.Head
:开头位置。RenderPosition.Foot
:结尾位置。例如,如果只想在列表的结尾位置渲染边界界面,可以这么做:
import { RenderPosition } from '@just4/virtual-list/types';
const renderer = {
// ...
renderBoundary(position, instance) {
if (position === RenderPosition.Foot) {
const div = document.createElement('div');
div.className = 'list-end';
div.innerHTML = '没有更多数据了';
div.onclick = function() {
// instance 即为虚拟滚动列表实例
instance.retryFetch();
};
return div;
}
}
};
virtualList.on('rendered', function(args) {
console.dir(args);
});
args 的类型说明见文档。
virtualList.on('item-click', function(args) {
console.dir(args);
});
args 的类型说明见文档。
virtualList.on('item-update', function(args) {
console.dir(args);
});
args 的类型说明见文档。
以下两种情况都会触发数据项的移除:
maxItemCount
;removeItem
或 removeItems
移除数据。virtualList.on('item-remove', function(args) {
console.dir(args);
});
args 的类型说明见文档。
可通过 items
属性获取当前数据项的集合。
// 第一个数据项
virtualList.items.first();
// 最后一个数据项
virtualList.items.last();
// 遍历所有数据项
for (let i = 0; i < virtualList.items.length; i++) {
console.dir(virtualList.items.get(i));
}
如果在加载数据的过程中出现异常(Promise 返回 rejected 状态),那么在该方向上的滚动加载将会停止。此时可以通过界面上的交互引导用户手动点击重试。在重试操作中调用 retryFetch
这个方法。
注意:retryFetch
方法仅在加载异常和到达边界两种状态下可用,其他状态下将不会执行任何操作。
inited
事件,在初始化结束后触发。inited
getter,用于获取当前是否已初始化。@just4/util/event
中的 PubSub
实现。VirtualListEvent
不再是枚举类型,事件名通过字符串指定即可。setOptions
更改 defaultView
选项。loadNextData
和 loadPreviousData
新增第二个参数,返回数据项的拷贝。loadNextData
和 loadPreviousData
,而不是传入 null
去调用它们。scrollToHead
和 scrollToFoot
新增 exceptState
参数,用于指定是否排除状态节点。removeItems
方法,用于移除多个数据项。resetBoundaryState
会触发刷新,重新请求初始数据。resetBoundaryState
重置状态之后,会检查当前是否处于预加载的范围内。