Skip to content

Commit 170a87d

Browse files
authored
perf: observe columns with one ResizeObserver instance (#706)
* perf: observe columns with one ResizeObserver instance * refactor: debounce replace with raf delay 2 frames
1 parent b0858ca commit 170a87d

File tree

7 files changed

+122
-29
lines changed

7 files changed

+122
-29
lines changed

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@
5555
"dependencies": {
5656
"@babel/runtime": "^7.10.1",
5757
"classnames": "^2.2.5",
58-
"rc-resize-observer": "^1.0.0",
58+
"rc-resize-observer": "^1.1.0",
5959
"rc-util": "^5.14.0",
6060
"shallowequal": "^1.1.0"
6161
},

src/Body/MeasureCell.tsx

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ export interface MeasureCellProps {
77
}
88

99
export default function MeasureCell({ columnKey, onColumnResize }: MeasureCellProps) {
10-
const cellRef = React.useRef<HTMLTableDataCellElement>();
10+
const cellRef = React.useRef<HTMLTableCellElement>();
1111

1212
React.useEffect(() => {
1313
if (cellRef.current) {
@@ -16,11 +16,7 @@ export default function MeasureCell({ columnKey, onColumnResize }: MeasureCellPr
1616
}, []);
1717

1818
return (
19-
<ResizeObserver
20-
onResize={({ offsetWidth }) => {
21-
onColumnResize(columnKey, offsetWidth);
22-
}}
23-
>
19+
<ResizeObserver data={columnKey}>
2420
<td ref={cellRef} style={{ padding: 0, border: 0, height: 0 }}>
2521
<div style={{ height: 0, overflow: 'hidden' }}>&nbsp;</div>
2622
</td>

src/Body/MeasureRow.tsx

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
import * as React from 'react';
2+
import ResizeObserver from 'rc-resize-observer';
3+
import MeasureCell from './MeasureCell';
4+
import raf from 'rc-util/lib/raf';
5+
6+
export interface MeasureCellProps {
7+
prefixCls: string;
8+
onColumnResize: (key: React.Key, width: number) => void;
9+
columnsKey: React.Key[];
10+
}
11+
12+
export default function MeasureRow({ prefixCls, columnsKey, onColumnResize }: MeasureCellProps) {
13+
// delay state update while resize continuously, e.g. window resize
14+
const resizedColumnsRef = React.useRef(new Map());
15+
const rafIdRef = React.useRef(null);
16+
17+
const delayOnColumnResize = () => {
18+
if (rafIdRef.current === null) {
19+
rafIdRef.current = raf(() => {
20+
resizedColumnsRef.current.forEach((width, columnKey) => {
21+
onColumnResize(columnKey, width);
22+
});
23+
resizedColumnsRef.current.clear();
24+
rafIdRef.current = null;
25+
}, 2);
26+
}
27+
};
28+
29+
React.useEffect(() => {
30+
return () => {
31+
raf.cancel(rafIdRef.current);
32+
};
33+
}, []);
34+
return (
35+
<tr
36+
aria-hidden="true"
37+
className={`${prefixCls}-measure-row`}
38+
style={{ height: 0, fontSize: 0 }}
39+
>
40+
<ResizeObserver.Collection
41+
onBatchResize={infoList => {
42+
infoList.forEach(({ data: columnKey, size }) => {
43+
resizedColumnsRef.current.set(columnKey, size.offsetWidth);
44+
});
45+
delayOnColumnResize();
46+
}}
47+
>
48+
{columnsKey.map(columnKey => (
49+
<MeasureCell key={columnKey} columnKey={columnKey} onColumnResize={onColumnResize} />
50+
))}
51+
</ResizeObserver.Collection>
52+
</tr>
53+
);
54+
}

src/Body/index.tsx

Lines changed: 6 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,10 @@ import ExpandedRow from './ExpandedRow';
55
import BodyContext from '../context/BodyContext';
66
import { getColumnsKey } from '../utils/valueUtil';
77
import ResizeContext from '../context/ResizeContext';
8-
import MeasureCell from './MeasureCell';
98
import BodyRow from './BodyRow';
109
import useFlattenRecords from '../hooks/useFlattenRecords';
1110
import HoverContext from '../context/HoverContext';
11+
import MeasureRow from './MeasureRow';
1212

1313
export interface BodyProps<RecordType> {
1414
data: readonly RecordType[];
@@ -102,19 +102,11 @@ function Body<RecordType>({
102102
<WrapperComponent className={`${prefixCls}-tbody`}>
103103
{/* Measure body column width with additional hidden col */}
104104
{measureColumnWidth && (
105-
<tr
106-
aria-hidden="true"
107-
className={`${prefixCls}-measure-row`}
108-
style={{ height: 0, fontSize: 0 }}
109-
>
110-
{columnsKey.map(columnKey => (
111-
<MeasureCell
112-
key={columnKey}
113-
columnKey={columnKey}
114-
onColumnResize={onColumnResize}
115-
/>
116-
))}
117-
</tr>
105+
<MeasureRow
106+
prefixCls={prefixCls}
107+
columnsKey={columnsKey}
108+
onColumnResize={onColumnResize}
109+
/>
118110
)}
119111

120112
{rows}

tests/FixedColumn-IE.spec.js

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import { spyElementPrototype } from 'rc-util/lib/test/domHook';
55
// eslint-disable-next-line @typescript-eslint/no-unused-vars
66
import { isStyleSupport } from 'rc-util/lib/Dom/styleChecker';
77
import Table from '../src';
8+
import RcResizeObserver from 'rc-resize-observer';
89

910
jest.mock('rc-util/lib/Dom/styleChecker', () => {
1011
return {
@@ -46,7 +47,16 @@ describe('Table.FixedColumn', () => {
4647
const wrapper = mount(<Table columns={columns} data={data} scroll={{ x: 1200 }} />);
4748

4849
act(() => {
49-
wrapper.find('table ResizeObserver').first().props().onResize({ width: 93, offsetWidth: 93 });
50+
wrapper
51+
.find(RcResizeObserver.Collection)
52+
.first()
53+
.props()
54+
.onBatchResize([
55+
{
56+
data: wrapper.find('table ResizeObserver').first().props().data,
57+
size: { width: 93, offsetWidth: 93 },
58+
},
59+
]);
5060
});
5161

5262
await act(async () => {

tests/FixedColumn.spec.js

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { act } from 'react-dom/test-utils';
44
import { resetWarned } from 'rc-util/lib/warning';
55
import { spyElementPrototype } from 'rc-util/lib/test/domHook';
66
import Table from '../src';
7+
import RcResizeObserver from 'rc-resize-observer';
78

89
describe('Table.FixedColumn', () => {
910
let domSpy;
@@ -59,10 +60,15 @@ describe('Table.FixedColumn', () => {
5960

6061
act(() => {
6162
wrapper
62-
.find('table ResizeObserver')
63+
.find(RcResizeObserver.Collection)
6364
.first()
6465
.props()
65-
.onResize({ width: 93, offsetWidth: 93 });
66+
.onBatchResize([
67+
{
68+
data: wrapper.find('table ResizeObserver').first().props().data,
69+
size: { width: 93, offsetWidth: 93 },
70+
},
71+
]);
6672
});
6773
await act(async () => {
6874
jest.runAllTimers();

tests/FixedHeader.spec.js

Lines changed: 40 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { mount } from 'enzyme';
33
import { act } from 'react-dom/test-utils';
44
import { spyElementPrototype } from 'rc-util/lib/test/domHook';
55
import Table, { INTERNAL_COL_DEFINE } from '../src';
6+
import RcResizeObserver from 'rc-resize-observer';
67

78
describe('Table.FixedHeader', () => {
89
let domSpy;
@@ -35,9 +36,24 @@ describe('Table.FixedHeader', () => {
3536
/>,
3637
);
3738

38-
wrapper.find('ResizeObserver').at(0).props().onResize({ width: 100, offsetWidth: 100 });
39-
wrapper.find('ResizeObserver').at(1).props().onResize({ width: 200, offsetWidth: 200 });
40-
wrapper.find('ResizeObserver').at(2).props().onResize({ width: 0, offsetWidth: 0 });
39+
wrapper
40+
.find(RcResizeObserver.Collection)
41+
.first()
42+
.props()
43+
.onBatchResize([
44+
{
45+
data: wrapper.find('ResizeObserver').at(0).props().data,
46+
size: { width: 100, offsetWidth: 100 },
47+
},
48+
{
49+
data: wrapper.find('ResizeObserver').at(1).props().data,
50+
size: { width: 200, offsetWidth: 200 },
51+
},
52+
{
53+
data: wrapper.find('ResizeObserver').at(2).props().data,
54+
size: { width: 0, offsetWidth: 0 },
55+
},
56+
]);
4157

4258
await act(async () => {
4359
jest.runAllTimers();
@@ -147,7 +163,16 @@ describe('Table.FixedHeader', () => {
147163
/>,
148164
);
149165

150-
wrapper.find('ResizeObserver').at(0).props().onResize({ width: 93, offsetWidth: 93 });
166+
wrapper
167+
.find(RcResizeObserver.Collection)
168+
.first()
169+
.props()
170+
.onBatchResize([
171+
{
172+
data: wrapper.find('ResizeObserver').at(0).props().data,
173+
size: { width: 93, offsetWidth: 93 },
174+
},
175+
]);
151176
await act(async () => {
152177
jest.runAllTimers();
153178
await Promise.resolve();
@@ -161,7 +186,17 @@ describe('Table.FixedHeader', () => {
161186
// Hide Table should not modify column width
162187
visible = false;
163188

164-
wrapper.find('ResizeObserver').at(0).props().onResize({ width: 0, offsetWidth: 0 });
189+
wrapper
190+
.find(RcResizeObserver.Collection)
191+
.first()
192+
.props()
193+
.onBatchResize([
194+
{
195+
data: wrapper.find('ResizeObserver').at(0).props().data,
196+
size: { width: 0, offsetWidth: 0 },
197+
},
198+
]);
199+
165200
act(() => {
166201
jest.runAllTimers();
167202
wrapper.update();

0 commit comments

Comments
 (0)