Skip to content

Commit

Permalink
Merge pull request #2572 from XiaoMi/perf/table
Browse files Browse the repository at this point in the history
perf(table): 表格性能优化
  • Loading branch information
solarjoker authored Aug 31, 2023
2 parents 0bd0b2f + eee5e6d commit 04266ad
Show file tree
Hide file tree
Showing 8 changed files with 101 additions and 72 deletions.
5 changes: 5 additions & 0 deletions .changeset/plenty-tools-prove.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@hi-ui/hiui": patch
---

Table perf: 表格性能优化
6 changes: 6 additions & 0 deletions .changeset/quick-socks-relax.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"@hi-ui/table": patch
"@hi-ui/tree-utils": patch
---

perf: 表格性能优化
81 changes: 43 additions & 38 deletions packages/ui/table/src/TableBody.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@ export const TableBody = forwardRef<HTMLDivElement | null, TableBodyProps>(
const {
columns,
leafColumns,
setMeasureRowElement,
isExpandTreeRows,
transitionData,
getColgroupProps,
Expand All @@ -34,6 +33,7 @@ export const TableBody = forwardRef<HTMLDivElement | null, TableBodyProps>(
sumRow,
colWidths,
virtual,
measureRowElementRef,
} = useTableContext()

const cls = cx(`${prefixCls}-body`)
Expand All @@ -53,8 +53,6 @@ export const TableBody = forwardRef<HTMLDivElement | null, TableBodyProps>(
)

const [scrollLeft, setScrollLeft] = useState(0)
// 是否使用虚拟滚动
const showVirtual = virtual && isArrayNonEmpty(transitionData)
const rowWidth = useMemo(() => {
let tmpWidth = 0
colWidths.forEach((width) => (tmpWidth += width))
Expand All @@ -68,7 +66,8 @@ export const TableBody = forwardRef<HTMLDivElement | null, TableBodyProps>(
},
[scrollBodyElementRef, onTableBodyScroll]
)
if (showVirtual) {

if (virtual) {
// TODO: avg和summay row的逻辑

const realHeight = scrollBodyElementRef.current?.getBoundingClientRect().height
Expand All @@ -90,39 +89,49 @@ export const TableBody = forwardRef<HTMLDivElement | null, TableBodyProps>(
overflowX: canScroll ? 'scroll' : undefined,
}}
>
<div
ref={(domElement) => {
setMeasureRowElement(domElement)
}}
style={{ height: 1, background: 'transparent' }}
></div>
<div ref={measureRowElementRef} style={{ height: 1, background: 'transparent' }}></div>
<div
ref={bodyTableRef}
style={{ height: 1, background: 'transparent', width: rowWidth }}
></div>
<div style={{ width: '100%', position: 'sticky', left: 0 }}>
<VirtualList
data={transitionData}
height={vMaxHeight}
itemHeight={10}
itemKey="id"
children={(row, index) => {
return (
<div style={{ position: 'relative', left: -scrollLeft }}>
<TableRow
// key={depth + index}
key={row.id}
// @ts-ignore
rowIndex={index}
rowData={row}
// expandedTree={isExpandTreeRows(row.id)}
{...getRequiredProps(row.id)}
/>
</div>
)
}}
/>
</div>
{isArrayNonEmpty(transitionData) ? (
<div style={{ width: '100%', position: 'sticky', left: 0 }}>
<VirtualList
data={transitionData}
height={vMaxHeight}
itemHeight={10}
itemKey="id"
children={(row, index) => {
return (
<div style={{ position: 'relative', left: -scrollLeft }}>
<TableRow
// key={depth + index}
key={row.id}
// @ts-ignore
rowIndex={index}
rowData={row}
// expandedTree={isExpandTreeRows(row.id)}
{...getRequiredProps(row.id)}
/>
</div>
)
}}
/>
</div>
) : (
renderEmptyContent({
className: `${prefixCls}-empty-content`,
colSpan: columns.length,
emptyContent,
...(scrollBodyElementRef.current
? {
scrollBodyWidth: window
.getComputedStyle(scrollBodyElementRef.current)
.getPropertyValue('width'),
}
: {}),
})
)}
</div>
)
}
Expand Down Expand Up @@ -156,11 +165,7 @@ export const TableBody = forwardRef<HTMLDivElement | null, TableBodyProps>(
{transitionData.map((row, index) => {
return (
<TableRow
ref={(dom) => {
if (index === 0) {
setMeasureRowElement(dom)
}
}}
ref={index === 0 ? measureRowElementRef : null}
// key={depth + index}
key={row.id}
// @ts-ignore
Expand Down
1 change: 0 additions & 1 deletion packages/ui/table/src/TableEmbedRow.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@ export const TableEmbedRow = ({
getEmbedPanelById,
isEmbedLoadingId,
onEmbedSwitch,
scrollBodyElementRef,
} = useTableContext()

const loading = isEmbedLoadingId(rowData.id)
Expand Down
21 changes: 13 additions & 8 deletions packages/ui/table/src/hooks/use-col-width.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,13 @@ export const useColWidth = ({
columns: TableColumnItem[]
virtual?: boolean
}) => {
const [measureRowElement, setMeasureRowElement] = React.useState<Element | null>(null)
const measureRowElementRef = React.useRef<Element | null>(null)
const [colWidths, setColWidths] = React.useState(() => {
return getGroupItemWidth(columns)
})

const getVirtualWidths = useCallback(() => {
const measureRowElement = measureRowElementRef.current
if (!measureRowElement) {
return getGroupItemWidth(columns)
}
Expand All @@ -32,6 +33,7 @@ export const useColWidth = ({
columns.forEach((columnItem: TableColumnItem) => {
totalWidth += columnItem.width || columnDefaultWidth
})

if (totalWidth < containerWidth) {
// 容器宽度大于设置的宽度总和时,col宽度等比分分配占满容器。
return columns.map((columnItem: TableColumnItem) => {
Expand All @@ -43,23 +45,27 @@ 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 的第一行)渲染,再次精确收集并设置每列宽度
*/
React.useEffect(() => {
let resizeObserver: ResizeObserver

const measureRowElement = measureRowElementRef.current

if (measureRowElement) {
const resizeObserver = new ResizeObserver(() => {
if (virtual) {
Expand All @@ -86,7 +92,7 @@ export const useColWidth = ({
}
}
// 测量元素在内容列为空时会是空,切换会使测量元素变化,导致后续的resize时间无法响应,此处测量元素变化时需要重新绑定
}, [measureRowElement, virtual])
}, [getVirtualWidths, virtual])

const [headerTableElement, setHeaderTableElement] = React.useState<HTMLTableElement | null>(null)

Expand Down Expand Up @@ -157,8 +163,7 @@ export const useColWidth = ({
)

return {
measureRowElement,
setMeasureRowElement,
measureRowElementRef,
onColumnResizable,
getColgroupProps,
setHeaderTableElement,
Expand Down
8 changes: 7 additions & 1 deletion packages/ui/table/src/hooks/use-expand.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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)
},
[]
Expand Down
30 changes: 16 additions & 14 deletions packages/ui/table/src/use-table.ts
Original file line number Diff line number Diff line change
Expand Up @@ -175,7 +175,7 @@ export const useTable = ({

// ************************ 列宽 resizable ************************ //

const { setMeasureRowElement, getColgroupProps, onColumnResizable, colWidths } = useColWidth({
const { measureRowElementRef, getColgroupProps, onColumnResizable, colWidths } = useColWidth({
data,
columns,
resizable,
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand All @@ -547,6 +549,7 @@ export const useTable = ({
}, [activeSorterColumn, activeSorterType, transitionData, columns])

return {
measureRowElementRef,
rootProps,
scrollWidth,
activeSorterColumn,
Expand All @@ -569,7 +572,6 @@ export const useTable = ({
// 行多选
rowSelection,
cacheData,
setMeasureRowElement,
leafColumns,
// ui
// 有表头分组那么也要 bordered
Expand Down
21 changes: 11 additions & 10 deletions packages/utils/tree-utils/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -711,35 +711,36 @@ export const getTreeNodesWithChildren = <T extends BaseTreeNodeData>(tree: T[])
/**
* 对平铺的树结构按照树形结构进行排序
* 因为 children 字段可能参与了其他逻辑处理,为不影响其他逻辑,增加一个 showChildren 字段,用于存放需要显示的子节点,并且按照 showChildren 转换成树结构
* @param flattedNode
* @param flattedTreeData
* @returns
*/
export const flattedTreeSort = <T extends BaseFlattedTreeNodeData<any> & { 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
}
}
}
}

const parentNodes = flattedNode.filter((d) => d.depth === 0)
const parentNodes = clonedFlattedTreeData.filter((d) => d.depth === 0)
const flattedNodes: T[] = []

const flattenNodes = (nodes: T[]) => {
Expand Down

0 comments on commit 04266ad

Please sign in to comment.