Optimization
I maintainer di solito rispondono entro 1 giorno
Nessuno ha ancora preso questa issue.
Valutazione
- Difficoltà
- 4/5
- Tempo stimato
- 3-5 giorni
- Idoneità per principianti
- 25/100
- Tipo di issue
- Bug
- Chiarezza
- Da chiarire
- Stato di attività
- Ferma
- Stack tecnologico
- typescript
- Ambito
- frontend, performance
Direzione di ricerca
Inizia con l'esempio Vue delle righe virtualizzate e l'esempio della selezione delle righe collegati nel report. Esegui il profiling della riproduzione fornita con 500.000 righe durante lo scorrimento e la selezione delle righe per identificare la causa del rallentamento. Il lavoro è completato quando la riproduzione consente uno scorrimento e una selezione delle righe fluidi e il comportamento rilevante è coperto da un test appropriato o da una misurazione documentata.
Scritto dal modello di indicizzazione a partire dal testo della issue.
Descrizione
Describe the bug
Row Selection and Virtualized Rows
The operation is a bit laggy. How can I optimize it
Your minimal, reproducible example
https://tanstack.com/table/latest/docs/framework/vue/examples/virtualized-rows
Steps to reproduce
https://tanstack.com/table/latest/docs/framework/vue/examples/virtualized-rows
IndeterminateCheckbox from https://tanstack.com/table/latest/docs/framework/vue/examples/row-selection?panel=sandbox
<script setup lang="ts">
import './index.css';
import { computed, ref, h } from 'vue';
import {
type ColumnDef,
FlexRender,
useVueTable,
getCoreRowModel,
getSortedRowModel,
RowSelectionState,
createColumnHelper,
} from '@tanstack/vue-table';
import { useVirtualizer } from '@tanstack/vue-virtual';
import IndeterminateCheckbox from './IndeterminateCheckbox.vue';
import { makeData, type Person } from './makeData';
const search = ref('');
const rowSelection = ref<RowSelectionState>({});
const data = ref<Person[]>(makeData(500000));
const filteredData = computed<Person[]>(() => {
const searchValue = search.value.toLowerCase();
// If no search value is present, return all data
if (!searchValue) return data.value;
return data.value.filter((row) => {
return Object.values(row).some((value) => {
if (value instanceof Date) {
return value.toLocaleString().toLowerCase().includes(searchValue);
}
// Stringify the value and check if it contains the search term
return `${value}`.toLowerCase().includes(searchValue);
});
});
});
let searchTimeout: NodeJS.Timeout;
function handleDebounceSearch(ev: Event) {
if (searchTimeout) {
clearTimeout(searchTimeout);
}
searchTimeout = setTimeout(() => {
search.value = (ev?.target as HTMLInputElement)?.value ?? '';
}, 300);
}
const columnHelper = createColumnHelper<Person>();
const columns = [
{
id: 'select',
header: ({ table }: { table: any }) => {
return h(IndeterminateCheckbox, {
checked: table.getIsAllRowsSelected(),
indeterminate: table.getIsSomeRowsSelected(),
onChange: table.getToggleAllRowsSelectedHandler(),
});
},
cell: ({ row }: { row: any }) => {
return h(IndeterminateCheckbox, {
checked: row.getIsSelected(),
disabled: !row.getCanSelect(),
onChange: row.getToggleSelectedHandler(),
});
},
},
columnHelper.group({
header: 'Name',
footer: (props) => props.column.id,
columns: [
columnHelper.accessor('firstName', {
cell: (info) => info.getValue(),
footer: (props) => props.column.id,
}),
columnHelper.accessor((row) => row.lastName, {
id: 'lastName',
cell: (info) => info.getValue(),
header: () => 'Last Name',
footer: (props) => props.column.id,
}),
],
}),
columnHelper.group({
header: 'Info',
footer: (props) => props.column.id,
columns: [
columnHelper.accessor('age', {
header: () => 'Age',
footer: (props) => props.column.id,
}),
columnHelper.group({
header: 'More Info',
columns: [
columnHelper.accessor('visits', {
header: () => 'Visits',
footer: (props) => props.column.id,
}),
columnHelper.accessor('status', {
header: 'Status',
footer: (props) => props.column.id,
}),
columnHelper.accessor('progress', {
header: 'Profile Progress',
footer: (props) => props.column.id,
}),
],
}),
],
}),
];
const table = useVueTable({
get data() {
return filteredData.value;
},
columns: columns,
state: {
get rowSelection() {
return rowSelection.value;
},
},
enableRowSelection: true, //enable row selection for all rows
// enableRowSelection: row => row.original.age > 18, // or enable row selection conditionally per row
onRowSelectionChange: (updateOrValue) => {
rowSelection.value =
typeof updateOrValue === 'function'
? updateOrValue(rowSelection.value)
: updateOrValue;
},
getCoreRowModel: getCoreRowModel(),
getSortedRowModel: getSortedRowModel(),
debugTable: false,
});
const rows = computed(() => table.getRowModel().rows);
//The virtualizer needs to know the scrollable container element
const tableContainerRef = ref<HTMLDivElement | null>(null);
const rowVirtualizerOptions = computed(() => {
return {
count: rows.value.length,
estimateSize: () => 33, //estimate row height for accurate scrollbar dragging
getScrollElement: () => tableContainerRef.value,
overscan: 5,
};
});
const rowVirtualizer = useVirtualizer(rowVirtualizerOptions);
const virtualRows = computed(() => rowVirtualizer.value.getVirtualItems());
const totalSize = computed(() => rowVirtualizer.value.getTotalSize());
function measureElement(el?: Element) {
if (!el) {
return;
}
rowVirtualizer.value.measureElement(el);
return undefined;
}
</script>
<template>
<div>
<p class="text-center">
For tables, the basis for the offset of the translate css function is from
the row's initial position itself. Because of this, we need to calculate
the translateY pixel count different and base it off the the index.
</p>
<h1 class="text-3xl font-bold text-center">Virtualized Rows</h1>
<div style="margin: 0 auto; width: min-content">
<input
:modelValue="search"
@input="handleDebounceSearch"
placeholder="Search"
class="p-2"
/>
{{ rows.length.toLocaleString() }} results
</div>
</div>
<div
class="container"
ref="tableContainerRef"
:style="{
overflow: 'auto', //our scrollable table container
position: 'relative', //needed for sticky header
height: '800px', //should be a fixed height
}"
>
<div :style="{ height: `${totalSize}px` }">
<!-- Even though we're still using sematic table tags, we must use CSS grid and flexbox for dynamic row heights -->
<table :style="{ display: 'grid' }">
<thead
:style="{
display: 'grid',
position: 'sticky',
top: 0,
zIndex: 1,
}"
>
<tr
v-for="headerGroup in table.getHeaderGroups()"
:key="headerGroup.id"
:style="{ display: 'flex', width: '100%' }"
>
<th
v-for="header in headerGroup.headers"
:key="header.id"
:colspan="header.colSpan"
:style="{ width: `${header.getSize()}px` }"
>
<div
v-if="!header.isPlaceholder"
:class="{
'cursor-pointer select-none': header.column.getCanSort(),
}"
@click="(e) => header.column.getToggleSortingHandler()?.(e)"
>
<FlexRender
:render="header.column.columnDef.header"
:props="header.getContext()"
/>
<span v-if="header.column.getIsSorted() === 'asc'"> 🔼</span>
<span v-if="header.column.getIsSorted() === 'desc'"> 🔽</span>
</div>
</th>
</tr>
</thead>
<tbody
:style="{
display: 'grid',
height: `${totalSize}px`, //tells scrollbar how big the table is
position: 'relative', //needed for absolute positioning of rows
}"
>
<tr
v-for="vRow in virtualRows"
:data-index="
vRow.index /* needed for dynamic row height measurement*/
"
:ref="measureElement /*measure dynamic row height*/"
:key="rows[vRow.index].id"
:style="{
display: 'flex',
position: 'absolute',
transform: `translateY(${vRow.start}px)`, //this should always be a `style` as it changes on scroll
width: '100%',
}"
>
<td
v-for="cell in rows[vRow.index].getVisibleCells()"
:key="cell.id"
:style="{
display: 'flex',
width: `${cell.column.getSize()}px`,
}"
>
<FlexRender
:render="cell.column.columnDef.cell"
:props="cell.getContext()"
/>
</td>
</tr>
</tbody>
</table>
</div>
</div>
</template>
Expected behavior
Smooth
How often does this bug happen?
None
Screenshots or Videos
No response
Platform
OS: [ macOS 14.6.1 ] - Browser: [Chrome 139.0.7258.155 ]
tanstack-virtual version
latest
TypeScript version
No response
Additional context
No response
Terms & Code of Conduct
- I agree to follow this project's Code of Conduct
- I understand that if my bug cannot be reliable reproduced in a debuggable environment, it will probably not be fixed and this issue may even be closed.
- Lingua principale
- TypeScript
- Stelle
- 7.1k
- Fork
- 472
- Merge medio
- 1g 1h
- PR unite (30g)
- 11
Preparare l'ambiente
- Nessun Dockerfile né file Docker Compose
- Ha un modello di pull request
- Leggi la guida per i contributori
Come iniziare
- Leggi tutta la issue e poi la guida ai contributi del progetto.
- Commenta sulla issue per dire che te ne occupi tu — evita che due persone facciano lo stesso lavoro.
- Fai un fork del repository e lavora su un branch.
- Apri una pull request che faccia riferimento al numero della issue.
Altre issue di TanStack/virtual
-
measureElement caches 0 size for nodes that aren't attached yet (Vue: rows stack at start 0 and the list scrolls away on mount)Forse già presa @webn00b l’ha presa 11 giorni fa. Aperta
Difficoltà 2/5 1-3 ore Idoneità per principianti 78/100
I maintainer di solito rispondono entro 1 giorno
-
anchorTo: 'end' loses the end when the scroll element shrinksForse già presa @langleyd l’ha presa oggi. Aperta
Difficoltà 3/5 1-2 giorni Idoneità per principianti 68/100
I maintainer di solito rispondono entro 1 giorno
-
`scrollToIndex(last, { align: 'end' })` stays short of the end after a measured row grows (virtual-core ≥ 3.17.0)Forse già presa @minwookshin l’ha presa 8 giorni fa. Aperta
Difficoltà 4/5 3-5 giorni Idoneità per principianti 55/100
TanStack/virtual#1290 · 1 commento ·
I maintainer di solito rispondono entro 1 giorno
-
virtual-core: desktop Safari drops the prepend anchor write when history lands during the top rubber-band bounceForse già presa Una pull request collegata a questa issue è aperta o già unita. Aperta
Difficoltà 5/5 Più di una settimana Idoneità per principianti 38/100
I maintainer di solito rispondono entro 1 giorno
-
Difficoltà 4/5 3-5 giorni Idoneità per principianti 62/100
I maintainer di solito rispondono entro 1 giorno
Tutte le issue di TanStack/virtual
Issue simili
-
Difficoltà 2/5 1-3 ore Idoneità per principianti 68/100
rajbos/ai-engineering-fluency#2340 · 1 commento ·
I maintainer di solito rispondono entro 1 giorno
-
community documentation first-timers-only good first issue hacktoberfest help wanted low hanging fruit up-for-grabs
Difficoltà 1/5 Meno di un'ora Idoneità per principianti 70/100
lingdojo/kana-dojo#31864 · 1 commento · 5 reazioni ·
I maintainer di solito rispondono entro 1 giorno
-
Difficoltà 2/5 1-3 ore Idoneità per principianti 68/100
zenstackhq/zenstack#2873 ·
I maintainer di solito rispondono entro 1 giorno
-
CLI: TUI shows onboarding when the provider's API key is only in the environment (e.g. OPENROUTER_API_KEY)Forse già presa Una pull request collegata a questa issue è aperta o già unita. ApertaCLI
Difficoltà 2/5 1-3 ore Idoneità per principianti 67/100
cline/cline#14923 · 2 commenti ·
I maintainer di solito rispondono entro 1 giorno
-
Difficoltà 2/5 1-3 ore Idoneità per principianti 76/100
paperclipai/paperclip#15490 ·
I maintainer di solito rispondono entro 1 giorno