diff --git a/.changeset/plenty-tools-prove.md b/.changeset/plenty-tools-prove.md new file mode 100644 index 000000000..107a1459d --- /dev/null +++ b/.changeset/plenty-tools-prove.md @@ -0,0 +1,5 @@ +--- +"@hi-ui/hiui": patch +--- + +Table perf: 表格性能优化 diff --git a/.changeset/quick-socks-relax.md b/.changeset/quick-socks-relax.md new file mode 100644 index 000000000..1bf9929fc --- /dev/null +++ b/.changeset/quick-socks-relax.md @@ -0,0 +1,6 @@ +--- +"@hi-ui/table": patch +"@hi-ui/tree-utils": patch +--- + +perf: 表格性能优化 diff --git a/packages/ui/table/src/TableBody.tsx b/packages/ui/table/src/TableBody.tsx index ad5c08548..b9ddaca93 100644 --- a/packages/ui/table/src/TableBody.tsx +++ b/packages/ui/table/src/TableBody.tsx @@ -17,7 +17,6 @@ export const TableBody = forwardRef( const { columns, leafColumns, - setMeasureRowElement, isExpandTreeRows, transitionData, getColgroupProps, @@ -34,6 +33,7 @@ export const TableBody = forwardRef( sumRow, colWidths, virtual, + measureRowElementRef, } = useTableContext() const cls = cx(`${prefixCls}-body`) @@ -53,8 +53,6 @@ export const TableBody = forwardRef( ) const [scrollLeft, setScrollLeft] = useState(0) - // 是否使用虚拟滚动 - const showVirtual = virtual && isArrayNonEmpty(transitionData) const rowWidth = useMemo(() => { let tmpWidth = 0 colWidths.forEach((width) => (tmpWidth += width)) @@ -68,7 +66,8 @@ export const TableBody = forwardRef( }, [scrollBodyElementRef, onTableBodyScroll] ) - if (showVirtual) { + + if (virtual) { // TODO: avg和summay row的逻辑 const realHeight = scrollBodyElementRef.current?.getBoundingClientRect().height @@ -90,39 +89,49 @@ export const TableBody = forwardRef( overflowX: canScroll ? 'scroll' : undefined, }} > -
{ - setMeasureRowElement(domElement) - }} - style={{ height: 1, background: 'transparent' }} - >
+
-
- { - return ( -
- -
- ) - }} - /> -
+ {isArrayNonEmpty(transitionData) ? ( +
+ { + return ( +
+ +
+ ) + }} + /> +
+ ) : ( + renderEmptyContent({ + className: `${prefixCls}-empty-content`, + colSpan: columns.length, + emptyContent, + ...(scrollBodyElementRef.current + ? { + scrollBodyWidth: window + .getComputedStyle(scrollBodyElementRef.current) + .getPropertyValue('width'), + } + : {}), + }) + )} ) } @@ -156,11 +165,7 @@ export const TableBody = forwardRef( {transitionData.map((row, index) => { return ( { - if (index === 0) { - setMeasureRowElement(dom) - } - }} + ref={index === 0 ? measureRowElementRef : null} // key={depth + index} key={row.id} // @ts-ignore diff --git a/packages/ui/table/src/TableEmbedRow.tsx b/packages/ui/table/src/TableEmbedRow.tsx index 3e0601644..f9028effa 100644 --- a/packages/ui/table/src/TableEmbedRow.tsx +++ b/packages/ui/table/src/TableEmbedRow.tsx @@ -21,7 +21,6 @@ export const TableEmbedRow = ({ getEmbedPanelById, isEmbedLoadingId, onEmbedSwitch, - scrollBodyElementRef, } = useTableContext() const loading = isEmbedLoadingId(rowData.id) diff --git a/packages/ui/table/src/hooks/use-col-width.ts b/packages/ui/table/src/hooks/use-col-width.ts index 01362c46d..ef65c8fc7 100644 --- a/packages/ui/table/src/hooks/use-col-width.ts +++ b/packages/ui/table/src/hooks/use-col-width.ts @@ -14,12 +14,13 @@ export const useColWidth = ({ columns: TableColumnItem[] virtual?: boolean }) => { - const [measureRowElement, setMeasureRowElement] = React.useState(null) + const measureRowElementRef = React.useRef(null) const [colWidths, setColWidths] = React.useState(() => { return getGroupItemWidth(columns) }) const getVirtualWidths = useCallback(() => { + const measureRowElement = measureRowElementRef.current if (!measureRowElement) { return getGroupItemWidth(columns) } @@ -32,6 +33,7 @@ export const useColWidth = ({ columns.forEach((columnItem: TableColumnItem) => { totalWidth += columnItem.width || columnDefaultWidth }) + if (totalWidth < containerWidth) { // 容器宽度大于设置的宽度总和时,col宽度等比分分配占满容器。 return columns.map((columnItem: TableColumnItem) => { @@ -43,16 +45,18 @@ export const useColWidth = ({ return columnItem.width || columnDefaultWidth }) } - }, [measureRowElement, columns]) + }, [columns]) useUpdateEffect(() => { if (virtual) { // 虚拟滚动的计算需要根据容器来做分配,不能使用没有witdh默认设置为0的方式来做表格平均分配 setColWidths(getVirtualWidths()) - } else { - setColWidths(getGroupItemWidth(columns)) } - }, [columns, getVirtualWidths, virtual]) + }, [getVirtualWidths, virtual]) + + useUpdateEffect(() => { + setColWidths(getGroupItemWidth(columns)) + }, [columns]) /** * 根据实际内容区(table 的第一行)渲染,再次精确收集并设置每列宽度 @@ -60,6 +64,8 @@ export const useColWidth = ({ React.useEffect(() => { let resizeObserver: ResizeObserver + const measureRowElement = measureRowElementRef.current + if (measureRowElement) { const resizeObserver = new ResizeObserver(() => { if (virtual) { @@ -86,7 +92,7 @@ export const useColWidth = ({ } } // 测量元素在内容列为空时会是空,切换会使测量元素变化,导致后续的resize时间无法响应,此处测量元素变化时需要重新绑定 - }, [measureRowElement, virtual]) + }, [getVirtualWidths, virtual]) const [headerTableElement, setHeaderTableElement] = React.useState(null) @@ -157,8 +163,7 @@ export const useColWidth = ({ ) return { - measureRowElement, - setMeasureRowElement, + measureRowElementRef, onColumnResizable, getColgroupProps, setHeaderTableElement, diff --git a/packages/ui/table/src/hooks/use-expand.ts b/packages/ui/table/src/hooks/use-expand.ts index 93130ce5c..f7d061670 100644 --- a/packages/ui/table/src/hooks/use-expand.ts +++ b/packages/ui/table/src/hooks/use-expand.ts @@ -60,7 +60,13 @@ export const useExpand = ( const trySetTransitionData = useCallback( (data: FlattedTableRowData[], expandedIds: React.ReactText[]) => { - const nextData = flattenTreeDataWithExpand(data, expandedIds) + let nextData = data + + // 当有 children 时,在构造新的数据,防止重复刷新组件 + if (data.some((item) => !!item.children && item.children.length > 0)) { + nextData = flattenTreeDataWithExpand(data, expandedIds) + } + setTransitionData(nextData) }, [] diff --git a/packages/ui/table/src/use-table.ts b/packages/ui/table/src/use-table.ts index 2b6f0a168..d1f85e6f2 100644 --- a/packages/ui/table/src/use-table.ts +++ b/packages/ui/table/src/use-table.ts @@ -175,7 +175,7 @@ export const useTable = ({ // ************************ 列宽 resizable ************************ // - const { setMeasureRowElement, getColgroupProps, onColumnResizable, colWidths } = useColWidth({ + const { measureRowElementRef, getColgroupProps, onColumnResizable, colWidths } = useColWidth({ data, columns, resizable, @@ -364,17 +364,19 @@ export const useTable = ({ const [scrollSize, setScrollSize] = useState({ scrollLeft: 0, scrollRight: 1 }) useEffect(() => { - // 计算冻结列的宽度 - // mutationObserver - const tableWidth = bodyTableRef.current?.getBoundingClientRect?.().width ?? 0 - const tableBodyWidth = scrollBodyElementRef.current?.getBoundingClientRect?.().width ?? 0 - const scrollRight = tableWidth - tableBodyWidth - // const scrollLeft = 0 - - setScrollSize((prev) => ({ - scrollLeft: prev.scrollLeft, - scrollRight, - })) + if (leftFrozenColKeys.length > 0 || rightFrozenColKeys.length > 0) { + // 计算冻结列的宽度 + // mutationObserver + const tableWidth = bodyTableRef.current?.getBoundingClientRect?.().width ?? 0 + const tableBodyWidth = scrollBodyElementRef.current?.getBoundingClientRect?.().width ?? 0 + const scrollRight = tableWidth - tableBodyWidth + // const scrollLeft = 0 + + setScrollSize((prev) => ({ + scrollLeft: prev.scrollLeft, + scrollRight, + })) + } }, [leftFrozenColKeys, rightFrozenColKeys]) // const canScroll = scrollSize.scrollRight > 0 @@ -528,7 +530,7 @@ export const useTable = ({ //* *************** 根据排序列处理数据 ************** *// const showData = useMemo(() => { - let _data = cloneTree(transitionData) + let _data = [...transitionData] if (activeSorterColumn) { const sorter = columns.filter((d) => d.dataKey === activeSorterColumn)[0]?.sorter @@ -547,6 +549,7 @@ export const useTable = ({ }, [activeSorterColumn, activeSorterType, transitionData, columns]) return { + measureRowElementRef, rootProps, scrollWidth, activeSorterColumn, @@ -569,7 +572,6 @@ export const useTable = ({ // 行多选 rowSelection, cacheData, - setMeasureRowElement, leafColumns, // ui // 有表头分组那么也要 bordered diff --git a/packages/utils/tree-utils/src/index.ts b/packages/utils/tree-utils/src/index.ts index 6dd2acb94..70f7275d9 100644 --- a/packages/utils/tree-utils/src/index.ts +++ b/packages/utils/tree-utils/src/index.ts @@ -711,27 +711,28 @@ export const getTreeNodesWithChildren = (tree: T[]) /** * 对平铺的树结构按照树形结构进行排序 * 因为 children 字段可能参与了其他逻辑处理,为不影响其他逻辑,增加一个 showChildren 字段,用于存放需要显示的子节点,并且按照 showChildren 转换成树结构 - * @param flattedNode + * @param flattedTreeData * @returns */ export const flattedTreeSort = & { showChildren?: T[] }>( - flattedNode: T[] + flattedTreeData: T[] ) => { - const len = flattedNode.length + const clonedFlattedTreeData = cloneTree(flattedTreeData) + const len = clonedFlattedTreeData.length for (let i = 0; i < len; i++) { - const item = flattedNode[i] + const item = clonedFlattedTreeData[i] const { depth, parent } = item if (depth !== 0) { for (let j = 0; j < len; j++) { - if (parent?.id === flattedNode[j].id) { - if (!flattedNode[j].showChildren) { - flattedNode[j].showChildren = [] + if (parent?.id === clonedFlattedTreeData[j].id) { + if (!clonedFlattedTreeData[j].showChildren) { + clonedFlattedTreeData[j].showChildren = [] } - !flattedNode[j].showChildren?.some((d) => d.id === item.id) && - flattedNode[j].showChildren?.push(item) + !clonedFlattedTreeData[j].showChildren?.some((d) => d.id === item.id) && + clonedFlattedTreeData[j].showChildren?.push(item) break } @@ -739,7 +740,7 @@ export const flattedTreeSort = & { showC } } - const parentNodes = flattedNode.filter((d) => d.depth === 0) + const parentNodes = clonedFlattedTreeData.filter((d) => d.depth === 0) const flattedNodes: T[] = [] const flattenNodes = (nodes: T[]) => {