Оптимизация рендеринга таблиц

Табличные интерфейсы являются одним из наиболее ресурсоёмких компонентов пользовательского интерфейса. Библиотека Element Plus предоставляет компонент el-table, который позволяет отображать большие объёмы структурированных данных, однако при работе с крупными наборами данных возникает проблема производительности.

Каждая строка таблицы представляет собой компонент Vue, содержащий реактивные зависимости, вычисления и DOM-узлы. При увеличении количества строк:

  • растёт количество реактивных наблюдателей;
  • увеличивается количество DOM-элементов;
  • усложняется процесс диффинга виртуального DOM;
  • возрастает стоимость перерисовки.

Поэтому при разработке интерфейсов с большими таблицами необходимо применять специальные техники оптимизации.

Основные направления оптимизации:

  • уменьшение количества одновременно отображаемых строк;
  • минимизация реактивности;
  • виртуализация списка;
  • оптимизация ключей и структуры данных;
  • управление обновлениями таблицы.

Минимизация реактивности данных

Vue отслеживает изменения всех реактивных свойств. Если таблица содержит тысячи строк, каждое изменение приводит к перерасчёту реактивной цепочки.

Использование shallowRef

Когда данные таблицы представляют собой большой массив, часто нет необходимости отслеживать изменения каждого вложенного свойства.

import { shallowRef } from 'vue'

const tableData = shallowRef([])

В этом случае Vue отслеживает только замену массива, а не изменения внутри элементов.

Преимущества:

  • меньше реактивных зависимостей;
  • ускоренная инициализация;
  • уменьшение нагрузки на систему реактивности.

Обновление данных выполняется через замену массива:

tableData.value = newData

Использование markRaw

Если объекты данных не требуют реактивности, их можно исключить из системы реактивности Vue.

import { markRaw } from 'vue'

const data = markRaw(largeDataset)

Это особенно полезно при работе с данными, полученными из API и не изменяющимися на клиенте.


Использование уникальных ключей строк

Каждая строка таблицы должна иметь стабильный ключ. В Element Plus это задаётся через свойство row-key.

<el-table
  :data="tableData"
  row-key="id"
>
</el-table>

Причины использования row-key:

  • ускорение алгоритма диффинга;
  • корректное обновление строк;
  • предотвращение полной перерисовки таблицы.

Без ключа Vue может заново пересоздавать строки, что значительно ухудшает производительность.


Постраничная загрузка данных (Pagination)

Одним из наиболее эффективных способов оптимизации является ограничение количества одновременно отображаемых строк.

В Element Plus используется компонент el-pagination.

<el-table :data="pagedData">
  <el-table-column prop="name" label="Name"/>
  <el-table-column prop="age" label="Age"/>
</el-table>

<el-pagination
  layout="prev, pager, next"
  :total="total"
  :page-size="pageSize"
  @current-change="handlePageChange"
/>

Логика обработки страниц:

const page = ref(1)
const pageSize = 20

const pagedData = computed(() => {
  const start = (page.value - 1) * pageSize
  return tableData.value.slice(start, start + pageSize)
})

function handlePageChange(newPage) {
  page.value = newPage
}

Преимущества:

  • уменьшение DOM;
  • ускорение рендеринга;
  • снижение потребления памяти.

Виртуализация таблиц

Когда необходимо отображать тысячи строк без пагинации, используется виртуализация.

Виртуализация означает, что в DOM одновременно находятся только видимые строки.

Element Plus предоставляет компонент:

el-table-v2

Он реализует виртуальный скролл.

Базовый пример

<el-table-v2
  :columns="columns"
  :data="data"
  :width="1000"
  :height="600"
/>

Описание параметров:

Параметр Назначение
columns конфигурация колонок
data массив строк
width ширина таблицы
height высота области прокрутки

Конфигурация колонок

const columns = [
  {
    key: 'id',
    dataKey: 'id',
    title: 'ID',
    width: 100
  },
  {
    key: 'name',
    dataKey: 'name',
    title: 'Name',
    width: 200
  },
  {
    key: 'email',
    dataKey: 'email',
    title: 'Email',
    width: 300
  }
]

Виртуализированная таблица работает по следующему принципу:

  1. вычисляется диапазон видимых строк;
  2. рендерится только этот диапазон;
  3. при прокрутке диапазон обновляется;
  4. создаётся иллюзия полного списка.

Это позволяет работать даже с десятками тысяч строк.


Оптимизация шаблонов колонок

Каждая колонка может содержать пользовательский шаблон. Сложные шаблоны увеличивают стоимость рендеринга.

Пример тяжёлого шаблона

<el-table-column label="User">
  <template #default="scope">
    <div class="user">
      <img :src="scope.row.avatar">
      <span>{{ scope.row.name }}</span>
      <span>{{ scope.row.email }}</span>
    </div>
  </template>
</el-table-column>

При большом количестве строк это приводит к созданию множества DOM-узлов.


Упрощение структуры DOM

Оптимизированный вариант:

<el-table-column
  prop="name"
  label="User"
/>

Минимизация вложенности:

  • меньше DOM-элементов;
  • быстрее diff;
  • быстрее layout браузера.

Отложенная загрузка данных (Lazy Loading)

Element Plus поддерживает ленивую загрузку строк в древовидных таблицах.

<el-table
  :data="tableData"
  lazy
  :load="loadChildren"
  row-key="id"
>
</el-table>

Функция загрузки:

function loadChildren(row, treeNode, resolve) {
  api.getChildren(row.id).then(data => {
    resolve(data)
  })
}

Преимущества:

  • данные загружаются только при раскрытии строки;
  • уменьшается первоначальная нагрузка;
  • ускоряется первый рендер.

Управление обновлениями таблицы

Иногда таблица обновляется слишком часто, например при фильтрации или поиске.

Использование debounce

import { debounce } from 'lodash'

const updateFilter = debounce((value) => {
  filter.value = value
}, 300)

Это уменьшает количество перерисовок.


Оптимизация вычисляемых свойств

Вычисляемые свойства могут пересчитываться слишком часто.

Неоптимальный вариант:

const filteredData = computed(() => {
  return tableData.value.filter(row => row.age > minAge.value)
})

Если таблица содержит тысячи строк, фильтрация выполняется каждый раз при изменении зависимости.

Оптимизация:

  • перенос вычислений на сервер;
  • использование мемоизации;
  • ограничение диапазона данных.

Кэширование строк

Если строки таблицы не изменяются, можно использовать кэширование компонентов.

В Vue применяется директива v-memo.

<template v-memo="[row.id]">
  <RowComponent :row="row"/>
</template>

Преимущества:

  • предотвращает повторный рендер;
  • снижает нагрузку на виртуальный DOM.

Управление высотой строк

Фиксированная высота строк значительно ускоряет виртуализацию.

<el-table-v2
  :row-height="40"
/>

Если высота строк динамическая, библиотека вынуждена выполнять дополнительные вычисления.


Отключение ненужных функций

Некоторые возможности таблицы увеличивают нагрузку:

  • сортировка;
  • фильтры;
  • выделение строк;
  • фиксированные колонки.

Если они не используются, их лучше не активировать.

Например, отключение сортировки:

<el-table-column
  prop="name"
  label="Name"
  sortable="false"
/>

Серверная обработка данных

Для очень больших таблиц (десятки тысяч записей) оптимальной стратегией является перенос вычислений на сервер.

На сервере выполняются:

  • фильтрация;
  • сортировка;
  • пагинация.

На клиент передаётся только необходимая часть данных.

Пример запроса:

async function fetchData(page, pageSize) {
  const response = await api.get('/users', {
    params: {
      page,
      pageSize
    }
  })

  tableData.value = response.data.items
}

Использование table-layout="fixed"

Фиксированная схема таблицы ускоряет расчёт ширины колонок браузером.

<el-table
  :data="tableData"
  table-layout="fixed"
>
</el-table>

Преимущества:

  • быстрее layout;
  • уменьшение reflow;
  • стабильная ширина колонок.

Контроль количества колонок

Большое количество колонок также влияет на производительность.

Каждая колонка добавляет:

  • дополнительные DOM-узлы;
  • вычисления ширины;
  • обработчики событий.

Оптимальные стратегии:

  • объединение данных в одну колонку;
  • использование всплывающих подсказок;
  • скрытие второстепенных колонок.

Использование v-once

Если строка никогда не изменяется, можно использовать директиву v-once.

<div v-once>
  {{ row.name }}
</div>

Элемент будет отрендерен только один раз.


Профилирование производительности

Для анализа производительности используются инструменты:

  • Vue Devtools
  • Chrome DevTools Performance
  • Lighthouse

Основные показатели:

  • время первого рендера;
  • количество DOM-узлов;
  • время обновления интерфейса.

Профилирование помогает выявить:

  • тяжёлые шаблоны;
  • лишние перерисовки;
  • неэффективные вычисления.

Стратегия масштабирования таблиц

При проектировании интерфейсов с большими таблицами обычно применяется комбинация методов:

  1. серверная пагинация;
  2. виртуализация;
  3. оптимизация реактивности;
  4. упрощение DOM;
  5. ленивые загрузки.

Такой подход позволяет поддерживать высокую производительность даже при работе с десятками тысяч записей и сложными интерфейсами управления данными.