Optimization
Mantenedores costumam responder em até 1 dia
Ninguém assumiu esta issue ainda.
Avaliação
- Dificuldade
- 4/5
- Tempo estimado
- 3-5 dias
- Facilidade para iniciantes
- 25/100
- Tipo de issue
- Bug
- Clareza
- Precisa de esclarecimento
- Status de atividade
- Estagnada
- Stack de tecnologia
- typescript
- Domínio
- frontend, performance
Direção de pesquisa
Comece pelo exemplo de Vue de linhas virtualizadas e pelo exemplo de seleção de linhas vinculados no relatório. Faça o profiling da reprodução fornecida com 500.000 linhas enquanto rola e seleciona linhas para identificar a origem da lentidão. Considera-se concluído quando a reprodução permite rolagem e seleção de linhas suaves, e o comportamento relevante está coberto por um teste apropriado ou uma medição documentada.
Escrita pelo modelo de indexação a partir do texto da issue.
Descrição
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.
- Linguagem predominante
- TypeScript
- Estrelas
- 7.1k
- Forks
- 472
- Merge médio
- 1d 1h
- PRs com merge (30d)
- 11
Preparar o ambiente
- Sem Dockerfile nem arquivo Docker Compose
- Tem um modelo de pull request
- Ler o guia de contribuição
Primeiros passos
- Leia a issue inteira e depois o guia de contribuição do projeto.
- Comente na issue dizendo que vai assumir — evita que duas pessoas façam o mesmo trabalho.
- Faça um fork do repositório e trabalhe em uma branch.
- Abra um pull request que referencie o número da issue.
Mais 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)Talvez já em andamento @webn00b assumiu há 11 dias. Aberta
Dificuldade 2/5 1-3 horas Facilidade para iniciantes 78/100
Mantenedores costumam responder em até 1 dia
-
anchorTo: 'end' loses the end when the scroll element shrinksTalvez já em andamento @langleyd assumiu hoje. Aberta
Dificuldade 3/5 1-2 dias Facilidade para iniciantes 68/100
Mantenedores costumam responder em até 1 dia
-
`scrollToIndex(last, { align: 'end' })` stays short of the end after a measured row grows (virtual-core ≥ 3.17.0)Talvez já em andamento @minwookshin assumiu há 9 dias. Aberta
Dificuldade 4/5 3-5 dias Facilidade para iniciantes 55/100
TanStack/virtual#1290 · 1 comentário ·
Mantenedores costumam responder em até 1 dia
-
virtual-core: desktop Safari drops the prepend anchor write when history lands during the top rubber-band bounceTalvez já em andamento Um pull request vinculado a esta issue está aberto ou já foi mesclado. Aberta
Dificuldade 5/5 Mais de uma semana Facilidade para iniciantes 38/100
Mantenedores costumam responder em até 1 dia
-
Dificuldade 4/5 3-5 dias Facilidade para iniciantes 62/100
Mantenedores costumam responder em até 1 dia
Todas as issues de TanStack/virtual
Issues semelhantes
-
Bump Firebase JS SDK (12.19.0 → 13.0.0)Talvez já em andamento @SelaseKay assumiu hoje. AbertaNeeds Attention type: enhancement
Dificuldade 2/5 1-3 horas Facilidade para iniciantes 75/100
invertase/react-native-firebase#9364 · 1 comentário ·
Mantenedores costumam responder em até 1 dia
-
Dificuldade 2/5 1-3 horas Facilidade para iniciantes 82/100
Mantenedores costumam responder em até 4 dias
-
[fullsend] E2E: rhdh-version-override — run-e2e.sh overrides RHDH_VERSION to non-existent 2.1Abertae2e-failure ready-to-code
Dificuldade 2/5 1-3 horas Facilidade para iniciantes 78/100
redhat-developer/rhdh-plugin-export-overlays#4261 · 1 comentário ·
Mantenedores costumam responder em até 1 dia
-
[Bug] 官网文档的图片挂了Abertabug
Dificuldade 2/5 1-3 horas Facilidade para iniciantes 66/100
Mantenedores costumam responder em até 1 dia
-
area:cli bug triage:in-progress
Dificuldade 1/5 1-3 horas Facilidade para iniciantes 82/100
Mantenedores costumam responder em até 1 dia