1. 逻辑功能简单介绍
- 逻辑并不复杂,首先获取到表格中可滚动那一级dom元素,然后监听它的滚动;当滚动到底了则触发我们自己的函数;
- 全篇幅最主要的一点就是 useEffect 中的内容,其他的都是看你怎么来了;
- useEffect 内容简单介绍:
(1)findNeedNode 函数: 递归查找 Table 元素中 className 为 ant-table-body 的节点(可滚动那一级dom元素)
(2)找到元素后添加监听滚动;
(3)判断屏幕高度和滚动条的高度,达到条件后则触发父级传入过来的滚动函数;
(4)功能拓展:实现了 findNeedNode 函数,其实就相当于实现了在ui框架组件内查找任意首次出现的 className。这里举个例子,比如说在 Table 中实现定位到某条内容;改装 findNeedNode函数,固定查找 ant-table-body className 修改为可传入式;定位到某条内容需要获取到这条内容的所在盒子的高度,而表格内部内容不可控,所以说需要获取表格内所有内容的高度进行加法运算(此处运算到需要定位的内容就停止),得到高度后,获取到 Table 中 tr 上一级的节点,设置这个节点的 scrollTop 属性进行定位。 - 注意如果想要出现滚动条需要设置 scroll 属性 给如 父级的高度就行了(document.getElementById(‘tableFather’)?.clientHeight)
- 如果介意代码野的可以看看总结的第三点和第四点
这块个人建议别看了,因为这块没有虚拟列表的优化,页面卡顿得厉害;去看看我虚拟列表实现吧
- React之antdesign官网虚拟列表二次封装(涵盖虚拟列表滚动加载)
2. 实现
import { useState, useEffect, useRef } from 'react';
import { Table } from 'antd'
const defaultTableInfo = {
loading: false,
tableData: [],
tableConfig: [
{
title: '名字',
dataIndex: 'name',
width: '180px',
align: 'center',
render: (text, record, index) => text
},
],
pagination: {
pageNum: 1,
pageSize: 10,
total: 0
}
}
const renderNumber = 0;
const Table = (props) => {
const { scroll, tableInfo = defaultTableInfo, onScrollBottom } = props;
const [pagination, setPagination] = useState(tableInfo.pagination);
const [columns, setColumns] = useState<any>(tableInfo.tableConfig);
const tableRef = useRef<any>(null)
useEffect(() => {
if (tableInfo.loading) return;
// 获取可滚动节点的父级节点
const findNeedNode: any = (node: any) => {
// 这个判断逻辑是看界面节点找到的可滚动那一级
if (node.className === 'ant-table-body') return node;
if (node.children) {
// 这块内容可以理解为数组中的map操作 return [].map(v=>xxxx);为啥不用map的原因是needNode不是一个真正的数组;
let needNode: any = [];
node.children.forEach((child: any) => {
needNode.push(findNeedNode(child))
})
return needNode;
}
}
setTimeout(() => {
// 最终处理完成的数据不知道在多少维;就当是N维数组;以flat(Infinity)降维处理;
const dom = findNeedNode(tableRef.current).flat(Infinity)[0];
//
if (!dom) return;
// 监听 屏幕是否到底了;
if (renderNumber > 1) return;
// 加载数据完毕后 只渲染第一次(addEventListener只让他生效一次!!!)
renderNumber++;
dom.addEventListener("scroll", () => {
const { pageSize, pageNum, total } = tableInfo.pagination;
const all = pageSize * pageNum;
const scrollTop = dom.scrollTop;
// 变量windowHeight是可视区的高度
const windowHeight = dom.clientHeight || dom.clientHeight;
// 变量scrollHeight是滚动条的总高度
const scrollHeight = dom.scrollHeight || dom.scrollHeight;
if (scrollTop + windowHeight + 1 > scrollHeight) {
if (all < total) {
// 表格置空不能有滚动加载
if (tableInfo.tableData.length < 1) return;
// 触发到底的函数
onScrollBottom({ ...pagination, pageNum: pageNum + 1 });
}
}
});
}, 0);
}, [tableInfo.loading])
return <Table
size="small"
rowKey="uuid"
ref={tableRef}
loading={tableInfo.loading}
pagination={false}
scroll={scrollInner}
columns={columns.filter((item: any) => !item.hidden)}
dataSource={tableInfo.tableData}
/>
}
3. 功能拓展
- findNeedNode 功能拓展:可查找ui框架内任意首次出现的className节点(温馨提示:此递归也可改成查找任意出现过的className)
const findNeedNode: any = (node: any, key: string = 'ant-table-body') => {
if (node.className === key) return node;
if (node.children) {
let needNode: any = [];
node.children.forEach((child: any) => {
needNode.push(findNeedNode(child, key))
})
return needNode;
}
}
- 定位功能可自己实现(有问题可以在评论区询问);
总结
- 上面代码是目测的,应该没有具体问题,反正中心点跟着我的介绍走,然后参考上面的示例,一般不会出啥问题。
- 如果有其他方法可以一起探讨一下!
- 上面这个因为使用了 addEventListener 所以说会有一个蛇皮问题,此组件如果有任何更新操作会导致整个组件重新渲染(别担心,正常一两三四五个表格在同一个页面根本看不出来,只是这个操作感觉很野的感觉),因为 react 是在全局的 document 上做的事件处理,我这个操作算是破坏这个流程的感觉,这块我倒是不知道咋个解决了,有大佬知道的求告知。
- 第三点的问题综合起来就是:在ui库里面的某个元素添加节点事件监听(addEventListener)后会出现的问题;