Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

perf(table): 表格性能优化 #2572

Merged
merged 1 commit into from
Aug 31, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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