Optimization
Los mantenedores suelen responder en 1 día
Nadie ha tomado este issue todavía.
Evaluación
- Dificultad
- 4/5
- Tiempo estimado
- 3-5 días
- Aptitud para principiantes
- 25/100
- Tipo de issue
- Error
- Claridad
- Necesita aclaración
- Estado de actividad
- Estancado
- Stack tecnológico
- typescript
- Área
- frontend, performance
Línea de trabajo
Comienza con el ejemplo de Vue de filas virtualizadas y el ejemplo de selección de filas enlazados en el informe. Perfila la reproducción proporcionada de 500.000 filas mientras te desplazas y seleccionas filas para identificar el origen de la latencia. Se considera completado cuando la reproducción permite desplazarse y seleccionar filas con fluidez, y el comportamiento relevante está cubierto por una prueba adecuada o una medición documentada.
Escrito por el modelo de indexación a partir del texto del issue.
Descripción
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.
- Lenguaje dominante
- TypeScript
- Estrellas
- 7.1k
- Forks
- 472
- Merge medio
- 1 d 1 h
- PR fusionados (30 d)
- 11
Preparar el entorno
- Sin Dockerfile ni archivo de Docker Compose
- Tiene una plantilla de pull request
- Leer la guía de contribución
Primeros pasos
- Lee el issue completo y luego la guía de contribución del proyecto.
- Comenta en el issue que vas a ocuparte — evita que dos personas hagan lo mismo.
- Haz un fork del repositorio y trabaja en una rama.
- Abre un pull request que haga referencia al número del issue.
Más de 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)Posiblemente ocupada @webn00b la tomó hace 11 días. Abierto
Dificultad 2/5 1-3 horas Aptitud para principiantes 78/100
Los mantenedores suelen responder en 1 día
-
anchorTo: 'end' loses the end when the scroll element shrinksPosiblemente ocupada @langleyd la tomó hoy. Abierto
Dificultad 3/5 1-2 días Aptitud para principiantes 68/100
Los mantenedores suelen responder en 1 día
-
`scrollToIndex(last, { align: 'end' })` stays short of the end after a measured row grows (virtual-core ≥ 3.17.0)Posiblemente ocupada @minwookshin la tomó hace 8 días. Abierto
Dificultad 4/5 3-5 días Aptitud para principiantes 55/100
TanStack/virtual#1290 · 1 comentario ·
Los mantenedores suelen responder en 1 día
-
virtual-core: desktop Safari drops the prepend anchor write when history lands during the top rubber-band bouncePosiblemente ocupada Un pull request vinculado a esta issue está abierto o ya se fusionó. Abierto
Dificultad 5/5 Más de una semana Aptitud para principiantes 38/100
Los mantenedores suelen responder en 1 día
-
Dificultad 4/5 3-5 días Aptitud para principiantes 62/100
Los mantenedores suelen responder en 1 día
Todos los issues de TanStack/virtual
Issues similares
-
enhancement
Dificultad 2/5 1-3 horas Aptitud para principiantes 88/100
Small-tailqwq/dsh-deep-whale#187 ·
Los mantenedores suelen responder en 1 día
-
Tela Marca manda conferir o campo "Razão social", que em Portugal se chama "Denominação social"Abierto
Dificultad 2/5 1-3 horas Aptitud para principiantes 82/100
melgarafael/DeskcommCRM#2503 ·
Los mantenedores suelen responder en 1 día
-
Dificultad 2/5 1-3 horas Aptitud para principiantes 74/100
0x80/isolate-package#218 ·
-
bug via-triage
Dificultad 2/5 1-3 horas Aptitud para principiantes 82/100
pingdotgg/t3code#16859 · 1 comentario ·
Los mantenedores suelen responder en 1 día
-
enhancement
Dificultad 2/5 1-3 horas Aptitud para principiantes 74/100
Los mantenedores suelen responder en 1 día