Як оптимізувати великі таблиці?
1. Не рендерити все одразу: пагінація
Найпростіше:
- показувати тільки частину даних (10-50 рядків на сторінку)
- решту - через кнопки/пагінацію/"показати ще"
<tr v-for="row in paginatedRows" :key="row.id">
...
</tr>const page = ref(1)
const perPage = 50
const paginatedRows = computed(() => {
const start = (page.value - 1) * perPage
return rows.value.slice(start, start + perPage)
})Ще краще - серверна пагінація: отримувати з бекенду тільки потрібну сторінку.
2. Віртуалізація / windowing (viewport-only rendering)
Для дуже великих таблиць (10k+ рядків) потрібна віртуальна прокрутка:
- у DOM рендеряться тільки видимі рядки + невеликий буфер зверху/знизу
- решта - просто порожнє місце з правильною висотою
У Vue зазвичай використовують бібліотеки на кшталт:
vue-virtual-scrollervue-virtual-scroll-list
Приклад ідеї (умовний):
<VirtualList
:data-key="'id'"
:data-sources="rows"
:data-component="RowComponent"
:kept-alive="50"
/>Це радикально знижує кількість DOM-елементів.
3. Мінімізувати реактивність даних
Якщо таблиця просто відображає дані і самі рядки не редагуються:
- не потрібно робити кожен рядок супер-реактивним
- можна використовувати:
markRawдля великих об'єктів/масивівshallowRefдля всього списку
import { shallowRef, markRaw } from 'vue'
const rows = shallowRef([])
// якщо приходять величезні об'єкти:
rows.value = markRaw(bigArray)Так Vue не буде глибоко відстежувати кожне вкладене поле.
4. Правильний key у v-for
Обов'язково:
<tr v-for="row in rows" :key="row.id">- стабільний унікальний
key→ Vue повторно використовує рядки - менше перестворень DOM-елементів
- краща продуктивність при фільтрації/сортуванні
Не можна використовувати індекс як key (:key="index"), якщо рядки можуть додаватися/видалятися/переставлятися.
5. Важка логіка - у computed, а не в шаблоні
Не робити так:
<td>{{ formatPrice(row.price) }}</td>
<td>{{ complexCalculation(row) }}</td>Краще:
- заздалегідь прорахувати дані в
computed - або підготувати поля до передавання в таблицю
const preparedRows = computed(() =>
rows.value.map(row => ({
...row,
priceLabel: formatPrice(row.price),
total: calcTotal(row)
}))
)6. Не створювати нові об'єкти/функції в шаблоні
Кожен рендер:
<Cell :options="{ align: 'right' }" />- створює новий об'єкт → Vue думає, що проп змінився → зайві оновлення.
Винеси такі дані:
const rightAlignOptions = { align: 'right' }<Cell :options="rightAlignOptions" />7. Оптимізувати фільтрацію та сортування
- фільтри/пошук → debounce на введення
- сортування/фільтрація → у
computed, а не вwatch deepна весь масив - при величезних даних - делегувати сортування і фільтрацію серверу
const debouncedQuery = ref('')
watch(query, debounce((v) => {
debouncedQuery.value = v
}, 300))8. Спростити вміст комірок
Всередині <td>:
- менше складних компонентів
- не рендерити те, що користувач все одно не бачить (умовні блоки, розкриття за кліком)
- ліниві деталі: розкриваємо подробиці/детальні картки тільки за запитом
9. Не тягнути все в глобальний стор "про всяк випадок"
Якщо величезна таблиця зберігається в глобальному store:
- будь-яка зміна стора може тригерити багато компонентів
- краще зберігати такі дані локально в сторінці/модулі
10. Візуально - скелетони і lazy-loading
З точки зору UX:
- показувати skeleton замість повної таблиці
- підвантажувати дані порціями
- не блокувати UI
Це не зовсім "оптимізація рендеру", але сильно покращує сприйняття.
Як це можна сказати на співбесіді (коротко):
Для оптимізації великих таблиць у Vue я:
- використовую пагінацію або віртуальний скрол, щоб не рендерити тисячі рядків одразу;
- мінімізую реактивність (shallowRef/markRaw для великих структур);
- стежу за
keyуv-for, щоб Vue повторно використовував рядки;- виношу важкі обчислення в
computedі не створюю нові об'єкти/функції в шаблоні;- за потреби роблю фільтрацію і сортування на сервері, а введення - з debounce.
Коротка відповідь
Для співбесідиКоротка відповідь допоможе вам впевнено відповідати на цю тему під час співбесіди.