问题描述
- 在我们一个后台向外提供接口的系统中,需要为接口提供配置参数,这些参数的格式基本就是对象格式,之前采用的方案是通过可编辑的表格来实现,每一行支持编辑和修改,支持添加子节点,但时候发现有些参数的嵌套层级特别深,要有6~7层,就会导致表单的元素的数量特别多,在这种情况下编辑表格,每输入一个字符,页面就会卡顿2~3秒。
问题分析
- 经过分析发现,当在输入框中输入时,页面会重新渲染整个Form表单,所以在数据量特别大的时候就会js就会画更长的时间处理渲染列表,主线程就会长时间被占用,阻塞了其他任务的执行,比如用户输入、动画、滚动,引起UI渲染和响应延迟,导致页面卡顿。
- 底层antDesign的Forn表单问题实现问题,antdesign3的Rc-Form和antDesign4的RC-From实现方式不同,antDesign3的form表单onchange函数一出发,就会刷新整个页面,导致页面卡顿。所以问题转化成了如何解决大数据树状的性能问题
解决方案
- Form分割:一般对长列表,可以采用form分割的形式,如果Table中每一行都是独立的Form,问题就解决了,但是Table表格中的渲染元素时在定义Table的Column时中使用Form.item定义的,并不能按照列进行分割。
- 虚拟列表:根据react-virtualized三方依赖库,自己开发一个虚拟列表。但是开发成本较高,使用三方包React Rsuite ,重构整个表格逻辑代码,将普通表格优化为虚拟列表。
- 虚拟列表的原理:
-
- 首先,根据可视区域的高度,以及每一行的高度计算出要渲染的节点数量。为了避免延迟加载页面会出现白屏闪烁的现象,可以在上下各流出一屏的缓存。
- 其次,根据单行元素的高度和数据量计算出元素容器的高度,得到每一行的占位符,此时元素容器会出现滚动条,检测元素同期的滚动条事件。
- 其次,根据scrollTop计算出滚动条中的滚轮距离顶部的距离,计算出当前可是区域应该显示数据开始位置以及结束为止,得到目前需要渲染的数据量。
- 最后,计算出每一个元素具体容器顶部的位置,得到需要渲染的元素在容器中的位置。
实现简单的虚拟列表

 => {
const containerRef = useRef(null);
const initItems = items;
const [visibleItems, setVisibleItems] = useState([]);
const [scrollTop, setScrollTop] = useState(0);
useEffect(() => {
const handleScroll = () => {
console.log('handleScroll');
const container = containerRef.current;
if (container) {
console.log('Container height:', container.clientHeight);
console.log('Scroll height:', container.scrollHeight);
const scrollTop = container.scrollTop;
setScrollTop(scrollTop);
console.log('scrollTop', scrollTop);
const startIdx = Math.floor(scrollTop / ITEM_HEIGHT);
const endIdx = Math.min(
items.length - 1,
startIdx + VISIBLE_ITEM_COUNT,
);
setVisibleItems(items.slice(startIdx, endIdx + 1));
}
};
const container = containerRef.current;
if (container) {
container.addEventListener('scroll', handleScroll);
handleScroll();
}
return () => {
if (container) {
container.removeEventListener('scroll', handleScroll);
}
};
}, [items]);
const containerHeight = items.length * ITEM_HEIGHT;
return (
{'scrollTop:' + scrollTop + ''}
ref={containerRef}
style={{
height: '400px',
overflowY: 'auto',
position: 'relative',
border: '1px solid #ddd',
}}
>
style={{
height: containerHeight,
position: 'relative',
backgroundColor: 'yellow',
}}
>
{visibleItems.map((item, index) => (
key={index}
style={{
position: 'absolute',
top: items.indexOf(item) * ITEM_HEIGHT + 'px',
height: ITEM_HEIGHT + 'px',
width: '100%',
boxSizing: 'border-box',
borderBottom: '1px solid #ddd',
}}
>
{`${item} top:${
initItems.indexOf(item) * ITEM_HEIGHT
}px ${initItems.indexOf(item)} ${ITEM_HEIGHT}`}
))}
);
};
export default VirtualList;