Работа с таблицами и списками

SvelteKit предоставляет широкие возможности для построения динамических интерфейсов с таблицами и списками, используя компонентный подход и реактивность, присущую Svelte. Основное внимание уделяется управлению состоянием данных, производительности рендеринга и интеграции с внешними библиотеками UI.


Динамические таблицы

В SvelteKit таблицы обычно создаются с использованием стандартных HTML-элементов <table>, <thead>, <tbody> и <tr>, но с реактивной привязкой данных через синтаксис {#each}. Пример базовой таблицы:

<script>
  let users = [
    { id: 1, name: 'Иван', age: 28 },
    { id: 2, name: 'Мария', age: 32 },
    { id: 3, name: 'Алексей', age: 24 }
  ];
</script>

<table class="table-auto w-full border-collapse border border-gray-300">
  <thead>
    <tr class="bg-gray-100">
      <th class="border p-2 text-left">ID</th>
      <th class="border p-2 text-left">Имя</th>
      <th class="border p-2 text-left">Возраст</th>
    </tr>
  </thead>
  <tbody>
    {#each users as user}
      <tr class="hover:bg-gray-50">
        <td class="border p-2">{user.id}</td>
        <td class="border p-2">{user.name}</td>
        <td class="border p-2">{user.age}</td>
      </tr>
    {/each}
  </tbody>
</table>

Ключевые моменты:

  • Использование {#each} обеспечивает автоматический рендер при изменении массива.
  • Реактивность Svelte обновляет таблицу без дополнительных методов setState.
  • CSS-классы можно подключать через Tailwind или другие UI-фреймворки для удобного оформления.

Сортировка и фильтрация данных

Для интерактивных таблиц часто требуется сортировка и фильтрация. Это можно реализовать через реактивные переменные и вычисляемые значения:

<script>
  let sortKey = 'name';
  let sortAsc = true;

  $: sortedUsers = [...users].sort((a, b) => {
    if(a[sortKey] < b[sortKey]) return sortAsc ? -1 : 1;
    if(a[sortKey] > b[sortKey]) return sortAsc ? 1 : -1;
    return 0;
  });

  function toggleSort(key) {
    if (sortKey === key) sortAsc = !sortAsc;
    else { sortKey = key; sortAsc = true; }
  }
</script>

<table class="table-auto w-full border border-gray-300">
  <thead>
    <tr>
      <th class="border p-2 cursor-pointer" on:click={() => toggleSort('id')}>ID</th>
      <th class="border p-2 cursor-pointer" on:click={() => toggleSort('name')}>Имя</th>
      <th class="border p-2 cursor-pointer" on:click={() => toggleSort('age')}>Возраст</th>
    </tr>
  </thead>
  <tbody>
    {#each sortedUsers as user}
      <tr>
        <td class="border p-2">{user.id}</td>
        <td class="border p-2">{user.name}</td>
        <td class="border p-2">{user.age}</td>
      </tr>
    {/each}
  </tbody>
</table>

Особенности реализации:

  • $: создаёт реактивное вычисление sortedUsers, которое автоматически обновляется при изменении массива users или ключа сортировки.
  • Функция toggleSort позволяет переключать направление сортировки без перерендеринга всей таблицы.

Пагинация

Для больших наборов данных пагинация уменьшает нагрузку на DOM. В SvelteKit это реализуется через реактивные диапазоны:

<script>
  let currentPage = 1;
  let itemsPerPage = 5;

  $: totalPages = Math.ceil(users.length / itemsPerPage);
  $: paginatedUsers = users.slice((currentPage - 1) * itemsPerPage, currentPage * itemsPerPage);

  function nextPage() {
    if(currentPage < totalPages) currentPage += 1;
  }

  function prevPage() {
    if(currentPage > 1) currentPage -= 1;
  }
</script>

<table class="table-auto w-full border">
  <thead>
    <tr>
      <th>ID</th>
      <th>Имя</th>
      <th>Возраст</th>
    </tr>
  </thead>
  <tbody>
    {#each paginatedUsers as user}
      <tr>
        <td>{user.id}</td>
        <td>{user.name}</td>
        <td>{user.age}</td>
      </tr>
    {/each}
  </tbody>
</table>

<div class="flex justify-between mt-2">
  <button on:click={prevPage} disabled={currentPage === 1}>Назад</button>
  <span>Страница {currentPage} из {totalPages}</span>
  <button on:click={nextPage} disabled={currentPage === totalPages}>Вперед</button>
</div>
  • slice эффективно разбивает массив на страницы без создания новых массивов.
  • Кнопки навигации используют простую логику отключения при достижении границ.

Интерактивные списки

Списки в SvelteKit строятся аналогично таблицам, с применением {#each} для генерации элементов <ul> или <ol>:

<script>
  let tasks = [
    { id: 1, title: 'Сделать домашку', done: false },
    { id: 2, title: 'Купить продукты', done: true },
    { id: 3, title: 'Позвонить другу', done: false }
  ];

  function toggleDone(task) {
    task.done = !task.done;
  }

  function removeTask(id) {
    tasks = tasks.filter(t => t.id !== id);
  }
</script>

<ul class="list-disc pl-5">
  {#each tasks as task (task.id)}
    <li class="flex justify-between items-center mb-1">
      <span class={task.done ? 'line-through text-gray-500' : ''}>{task.title}</span>
      <div class="space-x-2">
        <button on:click={() => toggleDone(task)}>{task.done ? 'Вернуть' : 'Выполнено'}</button>
        <button on:click={() => removeTask(task)}>Удалить</button>
      </div>
    </li>
  {/each}
</ul>

Выделенные моменты:

  • (task.id) используется как ключ для оптимизации рендеринга при изменении массива.
  • Реактивность Svelte позволяет мгновенно обновлять UI при изменении состояния задачи.
  • Применение классов line-through и text-gray-500 визуально выделяет выполненные элементы.

Интеграция с внешними UI-библиотеками

Библиотеки, такие как Svelte Material UI, Flowbite Svelte, SvelteStrap, позволяют создавать таблицы и списки с расширенной функциональностью: сортировка, фильтрация, пагинация, чекбоксы и модальные окна. Пример с Svelte Material UI:

<script>
  import { DataTable, DataTableHeader, DataTableRow, DataTableCell } from 'svelte-material-ui';
  let products = [
    { id: 1, name: 'Ноутбук', price: 1200 },
    { id: 2, name: 'Смартфон', price: 800 }
  ];
</script>

<DataTable>
  <DataTableHeader>
    <DataTableCell>ID</DataTableCell>
    <DataTableCell>Название</DataTableCell>
    <DataTableCell>Цена</DataTableCell>
  </DataTableHeader>
  {#each products as product}
    <DataTableRow>
      <DataTableCell>{product.id}</DataTableCell>
      <DataTableCell>{product.name}</DataTableCell>
      <DataTableCell>{product.price}</DataTableCell>
    </DataTableRow>
  {/each}
</DataTable>
  • Использование готовых компонентов сокращает объем ручного кода и обеспечивает консистентный дизайн.
  • Такие библиотеки поддерживают реактивное обновление данных и интеграцию с SvelteKit stores для глобального состояния.

Рекомендации по производительности

  1. Использовать ключи (item.id) в {#each} для предотвращения лишнего перерендеринга.
  2. Разбивать большие массивы на страницы или виртуализировать список для оптимизации DOM.
  3. Использовать реактивные вычисления $: для фильтров и сортировки, чтобы минимизировать ручное обновление состояния.
  4. При работе с UI-библиотеками проверять, поддерживают ли они SSR (Server-Side Rendering) для корректной работы в SvelteKit.