Skip to content

Commit 73a6b1e

Browse files
committed
improve performance and memory use
1 parent db745af commit 73a6b1e

File tree

12 files changed

+462
-365
lines changed

12 files changed

+462
-365
lines changed

examples/vanilla/basic/src/main.ts

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ type Person = {
1212
status: string
1313
progress: number
1414
}
15-
15+
/*
1616
const data: Person[] = [
1717
{
1818
firstName: 'tanner',
@@ -38,7 +38,10 @@ const data: Person[] = [
3838
status: 'Complicated',
3939
progress: 10,
4040
},
41-
]
41+
];
42+
*/
43+
44+
const data = new Array(50000).fill({});
4245

4346
const columnHelper = createColumnHelper<Person>()
4447

@@ -96,8 +99,15 @@ const renderTable = () => {
9699
theadElement.appendChild(trElement)
97100
})
98101

102+
103+
table.getRowModel();
104+
105+
106+
/*
99107
// Render table rows
100-
table.getRowModel().rows.forEach(row => {
108+
table.getRowModel().rows.forEach((row, idx) => {
109+
if (idx > 10) return;
110+
101111
const trElement = document.createElement('tr')
102112
row.getVisibleCells().forEach(cell => {
103113
const tdElement = document.createElement('td')
@@ -110,6 +120,7 @@ const renderTable = () => {
110120
tbodyElement.appendChild(trElement)
111121
})
112122
123+
/*
113124
// Render table footers
114125
table.getFooterGroups().forEach(footerGroup => {
115126
const trElement = document.createElement('tr')
@@ -123,6 +134,8 @@ const renderTable = () => {
123134
tfootElement.appendChild(trElement)
124135
})
125136
137+
*/
138+
126139
// Clear previous content and append new content
127140
const wrapperElement = document.getElementById('wrapper') as HTMLDivElement
128141
wrapperElement.innerHTML = ''

packages/table-core/src/core/row.ts

Lines changed: 136 additions & 83 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,136 @@ export interface CoreRow<TData extends RowData> {
9292
subRows: Row<TData>[]
9393
}
9494

95+
const rowProtosByTable = new WeakMap<Table<any>, any>()
96+
97+
98+
/**
99+
* Creates a table-specific row prototype object to hold shared row methods, including from all the
100+
* features that have been registered on the table.
101+
*/
102+
export function getRowProto<TData extends RowData>(table: Table<TData>) {
103+
let rowProto = rowProtosByTable.get(table)
104+
105+
if (!rowProto) {
106+
const obj: CoreRow<TData> = {
107+
// props are here only for typing; they are set on the instance at runtime
108+
id: 'unused',
109+
depth: 0,
110+
index: -1,
111+
original: undefined as TData,
112+
subRows: [],
113+
_valuesCache: {},
114+
_uniqueValuesCache: {},
115+
116+
117+
getValue(columnId: string) {
118+
if (this._valuesCache.hasOwnProperty(columnId)) {
119+
return this._valuesCache[columnId]
120+
}
121+
122+
const column = table.getColumn(columnId)
123+
124+
if (!column?.accessorFn) {
125+
return undefined
126+
}
127+
128+
this._valuesCache[columnId] = column.accessorFn(
129+
this.original as TData,
130+
this.index
131+
)
132+
133+
return this._valuesCache[columnId] as any
134+
},
135+
136+
getUniqueValues(columnId: string) {
137+
if (!this.hasOwnProperty('_uniqueValuesCache')) {
138+
// lazy-init cache on the instance
139+
this._uniqueValuesCache = {};
140+
}
141+
142+
if (this._uniqueValuesCache.hasOwnProperty(columnId)) {
143+
return this._uniqueValuesCache[columnId]
144+
}
145+
146+
const column = table.getColumn(columnId)
147+
148+
if (!column?.accessorFn) {
149+
return undefined
150+
}
151+
152+
if (!column.columnDef.getUniqueValues) {
153+
this._uniqueValuesCache[columnId] = [this.getValue(columnId)]
154+
return this._uniqueValuesCache[columnId]
155+
}
156+
157+
this._uniqueValuesCache[columnId] = column.columnDef.getUniqueValues(
158+
this.original as TData,
159+
this.index
160+
)
161+
162+
return this._uniqueValuesCache[columnId] as any
163+
},
164+
165+
renderValue(columnId: string) {
166+
return this.getValue(columnId) ?? table.options.renderFallbackValue
167+
},
168+
169+
getLeafRows() {
170+
return flattenBy(this.subRows, d => d.subRows)
171+
},
172+
173+
getParentRow() {
174+
return this.parentId ? table.getRow(this.parentId, true) : undefined
175+
},
176+
177+
getParentRows() {
178+
let parentRows: Row<TData>[] = []
179+
let currentRow = this
180+
while (true) {
181+
const parentRow = currentRow.getParentRow()
182+
if (!parentRow) break
183+
parentRows.push(parentRow)
184+
currentRow = parentRow
185+
}
186+
return parentRows.reverse()
187+
},
188+
189+
getAllCells: memo(
190+
function (this: Row<TData>) {
191+
return [this, table.getAllLeafColumns()]
192+
},
193+
(row, leafColumns) => {
194+
return leafColumns.map(column => {
195+
return createCell(table, row, column, column.id)
196+
})
197+
},
198+
getMemoOptions(table.options, 'debugRows', 'getAllCells')
199+
),
200+
201+
_getAllCellsByColumnId: memo(
202+
function (this: Row<TData>) {
203+
return [this.getAllCells()]
204+
},
205+
allCells => {
206+
return allCells.reduce(
207+
(acc, cell) => {
208+
acc[cell.column.id] = cell
209+
return acc
210+
},
211+
{} as Record<string, Cell<TData, unknown>>
212+
)
213+
},
214+
getMemoOptions(table.options, 'debugRows', 'getAllCellsByColumnId')
215+
),
216+
}
217+
218+
rowProtosByTable.set(table, obj)
219+
rowProto = obj
220+
}
221+
222+
return rowProto as CoreRow<TData>
223+
}
224+
95225
export const createRow = <TData extends RowData>(
96226
table: Table<TData>,
97227
id: string,
@@ -101,95 +231,18 @@ export const createRow = <TData extends RowData>(
101231
subRows?: Row<TData>[],
102232
parentId?: string
103233
): Row<TData> => {
104-
let row: CoreRow<TData> = {
234+
const row: CoreRow<TData> = Object.create(getRowProto(table))
235+
Object.assign(row, {
105236
id,
106237
index: rowIndex,
107238
original,
108239
depth,
109240
parentId,
110241
_valuesCache: {},
111-
_uniqueValuesCache: {},
112-
getValue: columnId => {
113-
if (row._valuesCache.hasOwnProperty(columnId)) {
114-
return row._valuesCache[columnId]
115-
}
116-
117-
const column = table.getColumn(columnId)
118-
119-
if (!column?.accessorFn) {
120-
return undefined
121-
}
122-
123-
row._valuesCache[columnId] = column.accessorFn(
124-
row.original as TData,
125-
rowIndex
126-
)
127-
128-
return row._valuesCache[columnId] as any
129-
},
130-
getUniqueValues: columnId => {
131-
if (row._uniqueValuesCache.hasOwnProperty(columnId)) {
132-
return row._uniqueValuesCache[columnId]
133-
}
134-
135-
const column = table.getColumn(columnId)
136-
137-
if (!column?.accessorFn) {
138-
return undefined
139-
}
140-
141-
if (!column.columnDef.getUniqueValues) {
142-
row._uniqueValuesCache[columnId] = [row.getValue(columnId)]
143-
return row._uniqueValuesCache[columnId]
144-
}
145-
146-
row._uniqueValuesCache[columnId] = column.columnDef.getUniqueValues(
147-
row.original as TData,
148-
rowIndex
149-
)
150-
151-
return row._uniqueValuesCache[columnId] as any
152-
},
153-
renderValue: columnId =>
154-
row.getValue(columnId) ?? table.options.renderFallbackValue,
155-
subRows: subRows ?? [],
156-
getLeafRows: () => flattenBy(row.subRows, d => d.subRows),
157-
getParentRow: () =>
158-
row.parentId ? table.getRow(row.parentId, true) : undefined,
159-
getParentRows: () => {
160-
let parentRows: Row<TData>[] = []
161-
let currentRow = row
162-
while (true) {
163-
const parentRow = currentRow.getParentRow()
164-
if (!parentRow) break
165-
parentRows.push(parentRow)
166-
currentRow = parentRow
167-
}
168-
return parentRows.reverse()
169-
},
170-
getAllCells: memo(
171-
() => [table.getAllLeafColumns()],
172-
leafColumns => {
173-
return leafColumns.map(column => {
174-
return createCell(table, row as Row<TData>, column, column.id)
175-
})
176-
},
177-
getMemoOptions(table.options, 'debugRows', 'getAllCells')
178-
),
179-
180-
_getAllCellsByColumnId: memo(
181-
() => [row.getAllCells()],
182-
allCells => {
183-
return allCells.reduce(
184-
(acc, cell) => {
185-
acc[cell.column.id] = cell
186-
return acc
187-
},
188-
{} as Record<string, Cell<TData, unknown>>
189-
)
190-
},
191-
getMemoOptions(table.options, 'debugRows', 'getAllCellsByColumnId')
192-
),
242+
})
243+
244+
if (subRows) {
245+
row.subRows = subRows
193246
}
194247

195248
for (let i = 0; i < table._features.length; i++) {

packages/table-core/src/features/ColumnFiltering.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -366,6 +366,7 @@ export const ColumnFiltering: TableFeature = {
366366
row: Row<TData>,
367367
_table: Table<TData>
368368
): void => {
369+
// TODO: move to a lazy-initialized proto getters
369370
row.columnFilters = {}
370371
row.columnFiltersMeta = {}
371372
},

packages/table-core/src/features/ColumnGrouping.ts

Lines changed: 25 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { RowModel } from '..'
1+
import { getRowProto, RowModel } from '..'
22
import { BuiltInAggregationFn, aggregationFns } from '../aggregationFns'
33
import {
44
AggregationFns,
@@ -353,30 +353,36 @@ export const ColumnGrouping: TableFeature = {
353353

354354
return table._getGroupedRowModel()
355355
}
356+
357+
Object.assign(getRowProto(table), {
358+
getIsGrouped() {
359+
return !!this.groupingColumnId
360+
},
361+
getGroupingValue(columnId) {
362+
if (this._groupingValuesCache.hasOwnProperty(columnId)) {
363+
return this._groupingValuesCache[columnId]
364+
}
365+
366+
const column = table.getColumn(columnId)
367+
368+
if (!column?.columnDef.getGroupingValue) {
369+
return this.getValue(columnId)
370+
}
371+
372+
this._groupingValuesCache[columnId] = column.columnDef.getGroupingValue(
373+
this.original
374+
)
375+
376+
return this._groupingValuesCache[columnId]
377+
},
378+
} as GroupingRow & Row<any>)
356379
},
357380

358381
createRow: <TData extends RowData>(
359382
row: Row<TData>,
360383
table: Table<TData>
361384
): void => {
362-
row.getIsGrouped = () => !!row.groupingColumnId
363-
row.getGroupingValue = columnId => {
364-
if (row._groupingValuesCache.hasOwnProperty(columnId)) {
365-
return row._groupingValuesCache[columnId]
366-
}
367-
368-
const column = table.getColumn(columnId)
369-
370-
if (!column?.columnDef.getGroupingValue) {
371-
return row.getValue(columnId)
372-
}
373-
374-
row._groupingValuesCache[columnId] = column.columnDef.getGroupingValue(
375-
row.original
376-
)
377-
378-
return row._groupingValuesCache[columnId]
379-
}
385+
// TODO: move to a lazy-initialized proto getter
380386
row._groupingValuesCache = {}
381387
},
382388

0 commit comments

Comments
 (0)