Skip to content

Commit 470e97c

Browse files
authored
feat: Table 优化大数据性能 (#11915)
1 parent f57010c commit 470e97c

File tree

8 files changed

+233
-86
lines changed

8 files changed

+233
-86
lines changed

examples/components/CRUD/TableAutoFill.jsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,7 @@ export default {
143143
{
144144
name: 'id',
145145
label: 'ID',
146+
width: 50,
146147
searchable: {
147148
type: 'input-text',
148149
name: 'id',

packages/amis-core/src/store/table.ts

Lines changed: 12 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -202,9 +202,7 @@ export const Row = types
202202
loaded: false, // 懒数据是否加载完了
203203
loading: false, // 懒数据是否正在加载
204204
error: '', // 懒数据加载失败的错误信息
205-
depth: types.number, // 当前children位于第几层,便于使用getParent获取最顶层TableStore
206-
appeared: true,
207-
lazyRender: false
205+
depth: types.number // 当前children位于第几层,便于使用getParent获取最顶层TableStore
208206
})
209207
.views(self => ({
210208
get parent() {
@@ -486,10 +484,6 @@ export const Row = types
486484
}
487485
},
488486

489-
markAppeared(value: any) {
490-
value && (self.appeared = !!value);
491-
},
492-
493487
markLoading(value: any) {
494488
self.loading = !!value;
495489
},
@@ -592,7 +586,7 @@ export const TableStore = iRendererStore
592586
exportExcelLoading: false,
593587
searchFormExpanded: false, // 用来控制搜索框是否展开了,那个自动根据 searchable 生成的表单 autoGenerateFilter
594588
lazyRenderAfter: 100,
595-
tableLayout: 'auto',
589+
tableLayoutConfig: 'auto',
596590
theadHeight: 0,
597591
persistKey: ''
598592
})
@@ -849,6 +843,14 @@ export const TableStore = iRendererStore
849843
return getEnv(self).translate;
850844
},
851845

846+
get tableLayout() {
847+
if (self.rows.length > self.lazyRenderAfter) {
848+
return 'fixed';
849+
}
850+
851+
return self.tableLayoutConfig;
852+
},
853+
852854
getSelectionUpperLimit,
853855

854856
get columnsKey() {
@@ -1219,8 +1221,8 @@ export const TableStore = iRendererStore
12191221
typeof config.lazyRenderAfter === 'number' &&
12201222
(self.lazyRenderAfter = config.lazyRenderAfter);
12211223

1222-
typeof config.tableLayout === 'string' &&
1223-
(self.tableLayout = config.tableLayout);
1224+
typeof config.tableLayoutConfig === 'string' &&
1225+
(self.tableLayoutConfig = config.tableLayoutConfig);
12241226

12251227
config.showIndex !== undefined && (self.showIndex = config.showIndex);
12261228
config.persistKey !== undefined && (self.persistKey = config.persistKey);
@@ -1613,21 +1615,6 @@ export const TableStore = iRendererStore
16131615
);
16141616

16151617
if (!allMatched) {
1616-
// 前 20 个直接渲染,后面的按需渲染
1617-
if (
1618-
self.lazyRenderAfter &&
1619-
self.falttenedRows.length > self.lazyRenderAfter
1620-
) {
1621-
for (
1622-
let i = self.lazyRenderAfter, len = self.falttenedRows.length;
1623-
i < len;
1624-
i++
1625-
) {
1626-
self.falttenedRows[i].appeared = false;
1627-
self.falttenedRows[i].lazyRender = true;
1628-
}
1629-
}
1630-
16311618
const expand = self.footable && self.footable.expand;
16321619
if (
16331620
expand === 'first' ||

packages/amis-ui/scss/components/_table.scss

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -184,6 +184,45 @@
184184
min-height: 0.01%;
185185
overflow-x: auto;
186186
transform: translateZ(0);
187+
188+
&.use-virtual-list {
189+
> table > .virtual-table-body-placeholder {
190+
> tr {
191+
padding: 0 !important;
192+
margin: 0 !important;
193+
border: 0 !important;
194+
background: transparent !important;
195+
}
196+
> tr > td {
197+
padding: 0 !important;
198+
margin: 0 !important;
199+
border: 0 !important;
200+
background: transparent !important;
201+
202+
> div {
203+
display: block;
204+
height: 0;
205+
width: 50px;
206+
position: sticky;
207+
left: 0;
208+
will-change: height;
209+
transform: translateZ(0);
210+
contain: content;
211+
}
212+
}
213+
}
214+
215+
> table > .virtual-table-body-placeholder.leading > tr > td > div {
216+
height: var(--Table-scroll-offset);
217+
}
218+
219+
> table > .virtual-table-body-placeholder.trailing > tr > td > div {
220+
height: calc(
221+
var(--Table-scroll-height) - var(--Table-frame-height) -
222+
var(--Table-scroll-offset)
223+
);
224+
}
225+
}
187226
}
188227

189228
&-content-colDragLine {

packages/amis/src/renderers/Table/TableBody.tsx

Lines changed: 14 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ import {createObject} from 'amis-core';
99
import {LocaleProps} from 'amis-core';
1010
import {ActionSchema} from '../Action';
1111
import type {IColumn, IRow, ITableStore, TestIdBuilder} from 'amis-core';
12+
import flatten from 'lodash/flatten';
13+
import {VirtualTableBody} from './VirtualTableBody';
1214

1315
export interface TableBodyProps extends LocaleProps {
1416
store: ITableStore;
@@ -364,6 +366,7 @@ export class TableBody<
364366
classnames: cx,
365367
className,
366368
render,
369+
store,
367370
rows,
368371
columns,
369372
rowsProps,
@@ -372,16 +375,17 @@ export class TableBody<
372375
translate: __
373376
} = this.props;
374377

375-
return (
376-
<tbody className={className}>
377-
{rows.length ? (
378-
<>
379-
{this.renderSummary('prefix', prefixRow)}
380-
{this.renderRows(rows, columns, rowsProps)}
381-
{this.renderSummary('affix', affixRow)}
382-
</>
383-
) : null}
384-
</tbody>
378+
const doms: React.ReactNode[] = flatten(
379+
[]
380+
.concat(this.renderSummary('prefix', prefixRow) as any)
381+
.concat(this.renderRows(rows, columns, rowsProps) as any)
382+
.concat(this.renderSummary('affix', affixRow) as any)
383+
).filter(Boolean);
384+
385+
return rows.length > store.lazyRenderAfter ? (
386+
<VirtualTableBody rows={doms} store={this.props.store} />
387+
) : (
388+
<tbody className={className}>{doms}</tbody>
385389
);
386390
}
387391
}

packages/amis/src/renderers/Table/TableRow.tsx

Lines changed: 27 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,6 @@ export class TableRow<
6767
moved: boolean;
6868
depth: number;
6969
expandable: boolean;
70-
appeard?: boolean;
7170
loading?: boolean;
7271
error?: string;
7372
checkdisable: boolean;
@@ -205,7 +204,6 @@ export class TableRow<
205204
moved,
206205
depth,
207206
expandable,
208-
appeard,
209207
checkdisable,
210208
trRef,
211209
isNested,
@@ -263,28 +261,22 @@ export class TableRow<
263261
</th>
264262
) : null}
265263

266-
{appeard ? (
267-
renderCell(
268-
`${regionPrefix}${itemIndex}/${column.index}`,
269-
column,
270-
item,
271-
{
272-
...rest,
273-
width: null,
274-
rowIndex: itemIndex,
275-
rowIndexPath: item.path,
276-
colIndex: column.index,
277-
rowPath,
278-
key: column.id,
279-
onAction: this.handleAction,
280-
onQuickChange: this.handleQuickChange,
281-
onChange: this.handleChange
282-
}
283-
)
284-
) : (
285-
<td key={column.id}>
286-
<div className={cx('Table-emptyBlock')}>&nbsp;</div>
287-
</td>
264+
{renderCell(
265+
`${regionPrefix}${itemIndex}/${column.index}`,
266+
column,
267+
item,
268+
{
269+
...rest,
270+
width: null,
271+
rowIndex: itemIndex,
272+
rowIndexPath: item.path,
273+
colIndex: column.index,
274+
rowPath,
275+
key: column.id,
276+
onAction: this.handleAction,
277+
onQuickChange: this.handleQuickChange,
278+
onChange: this.handleChange
279+
}
288280
)}
289281
</tr>
290282
))}
@@ -331,23 +323,17 @@ export class TableRow<
331323
{...testIdBuilder?.(rowPath)?.getTestId()}
332324
>
333325
{columns.map(column =>
334-
appeard ? (
335-
renderCell(`${itemIndex}/${column.index}`, column, item, {
336-
...rest,
337-
rowIndex: itemIndex,
338-
colIndex: column.index,
339-
rowIndexPath: item.path,
340-
rowPath,
341-
key: column.id,
342-
onAction: this.handleAction,
343-
onQuickChange: this.handleQuickChange,
344-
onChange: this.handleChange
345-
})
346-
) : column.name && item.rowSpans[column.name] === 0 ? null : (
347-
<td key={column.id}>
348-
<div className={cx('Table-emptyBlock')}>&nbsp;</div>
349-
</td>
350-
)
326+
renderCell(`${itemIndex}/${column.index}`, column, item, {
327+
...rest,
328+
rowIndex: itemIndex,
329+
colIndex: column.index,
330+
rowIndexPath: item.path,
331+
rowPath,
332+
key: column.id,
333+
onAction: this.handleAction,
334+
onQuickChange: this.handleQuickChange,
335+
onChange: this.handleChange
336+
})
351337
)}
352338
</tr>
353339
);
@@ -364,16 +350,9 @@ export default observer((props: TableRowProps) => {
364350
store.canAccessSuperData ||
365351
columns.some(item => item.pristine.canAccessSuperData);
366352

367-
const {ref, inView} = useInView({
368-
threshold: 0,
369-
onChange: item.markAppeared,
370-
skip: !item.lazyRender
371-
});
372-
373353
return (
374354
<TableRow
375355
{...props}
376-
trRef={ref}
377356
expanded={item.expanded}
378357
parentExpanded={parent?.expanded}
379358
id={item.id}
@@ -391,7 +370,6 @@ export default observer((props: TableRowProps) => {
391370
// data 在 TableRow 里面没有使用,这里写上是为了当列数据变化的时候 TableRow 重新渲染,
392371
// 不是 item.locals 的原因是 item.locals 会变化多次,比如父级上下文变化也会进来,但是 item.data 只会变化一次。
393372
data={canAccessSuperData ? item.locals : item.data}
394-
appeard={item.lazyRender ? item.appeared || inView : true}
395373
isNested={store.isNested}
396374
/>
397375
);
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import React from 'react';
2+
import Cell from './Cell';
3+
import {useInView} from 'react-intersection-observer';
4+
5+
export default function VCell(props: any) {
6+
const {ref, inView} = useInView({
7+
threshold: 0,
8+
triggerOnce: true
9+
});
10+
11+
return inView ? (
12+
<Cell {...props} />
13+
) : (
14+
<td
15+
ref={ref}
16+
rowSpan={props.rowSpan > 1 ? props.rowSpan : undefined}
17+
style={props.style}
18+
className={props.className}
19+
>
20+
&nbsp;
21+
</td>
22+
);
23+
}

0 commit comments

Comments
 (0)