Привет. Недавно пришлось повозиться с @tanstack/vue-table. Задача была стандартная: нужна таблица с сортировкой, фильтрами и редактированием ячеек. Казалось бы - идеальное время подключить готовое решение. Но не всё так гладко. Делюсь мыслями, граблями и тем, как я в итоге выкрутился.

Что это вообще такое

Если коротко: это headless-библиотека. То есть она даёт логику, а как это будет выглядеть - решаешь ты сам. Никаких готовых тем, никаких дефолтных стилей. Полный контроль, но и вся ответственность на тебе.

Звучит круто, правда? Пока не начнёшь разбираться.


Первое разочарование: документация

Открываешь доки после Quasar или AG Grid - и как будто попал в другой мир. Информация разбросана, примеры часто для React, а про Vue - как будто в последнюю очередь дописали.

И это не паранойя. Библиотека писалась под React, а Vue-версия - это своего рода «порт». Из-за этого некоторые вещи чувствуются чужеродно. Например, хук useVueTable ведёт себя так, будто ожидает, что данные будут неизменяемыми (immutable), а во Vue 3 мы привыкли мутировать объекты через прокси.


Главная проблема: реактивность и мутации

Во Vue ты можешь просто сделать list.push(...) - и интерфейс обновится. В мире TanStack так не работает. Библиотека отслеживает изменения по ссылке на объект. Если ссылка не поменялась - для неё ничего не произошло.

Что это значит на практике:

  • Добавил элемент через push() - таблица не увидела.

  • Поменял поле в объекте - ячейка не перерендерилась через flexRender.

  • Хочешь редактирование «на лету»? Готовься к костылям.

Люди уже наступали на эти грабли:


Как я это обошёл (рабочий подход)

Вместо того чтобы бороться с библиотекой, я принял правила игры: данные для таблицы - только через замену ссылки. И вынес эту логику в отдельный хук.

Вот упрощённый пример того, как я организую работу с данными:

// useTableDataSync.ts
import { shallowRef, watch } from 'vue'

export function useTableDataSync<T extends { id: string | number }>(
  source: () => T[],
  idField: keyof T = 'id'
) {
  const tableData = shallowRef<T[]>([...source()])

  watch(source, (next) => {
    // Быстрый путь: разная длина - точно есть изменения
    if (next.length !== tableData.value.length) {
      tableData.value = [...next]
      return
    }

    // Сравниваем по ключу, чтобы не триггерить лишние обновления
    const changed = next.some((item, i) => {
      const prev = tableData.value[i]
      return !prev || item[idField] !== prev[idField]
    })

    if (changed) {
      tableData.value = [...next]
    }
  }, { deep: true })

  return tableData
}

А в компоненте использую так:

<script setup lang="ts" generic="T extends { id: string | number }">
import { computed } from 'vue'
import { useVueTable, getCoreRowModel } from '@tanstack/vue-table'
import { useTableDataSync } from './useTableDataSync'

const props = defineProps<{
  rows: T[]
  columns: ColumnDef<T>[]
  rowKey?: keyof T
}>()

const data = useTableDataSync(() => props.rows, props.rowKey ?? 'id')

const table = useVueTable({
  get data() { return data.value },
  columns: props.columns,
  getRowId: (row) => String(row[props.rowKey ?? 'id']),
  getCoreRowModel: getCoreRowModel(),
})
</script>

Так я изолировал «неудобную» часть и больше не думаю о мутациях внутри компонента.


Про FlexRender: зачем он?

Честно - не понял. FlexRender пытается абстрагировать рендер ячеек, но во Vue это только усложняет. Автокомплит перестаёт работать, типы «плывут», а в шаблонах начинается магия с h() и component :is.

Я просто рендерю таблицу обычным v-for. Для кастомных ячеек использую слоты - так понятнее, типизируется лучше и команда не страдает.


Когда стоит брать TanStack Table

Не буду говорить, что библиотека плохая. Она мощная. Но подходит не всем.

Бери, если:

  • Нужна сложная логика: серверная сортировка, пагинация, группировка.

  • Готов потратить время на настройку и обёртки.

  • В команде есть люди, которые уже работали с TanStack.

Не бери, если:

  • Нужна простая таблица «показать данные».

  • Хочется быстро сделать и забыть.

  • Команда не готова разбираться с особенностями реактивности.

Мой чеклист перед подключением

  1. Попробуй сделать таблицу на чистом Vue за час. Если получилось - зачем усложнять?

  2. Проверь, нужна ли тебе вся эта мощь. Часто 80% функционала достигается обычными хуками.

  3. Если всё же берёшь TanStack - сразу выдели «прослойку» для синхронизации данных. Не смешивай бизнес-логику с логикой таблицы.

  4. Тестируй редактирование ячеек на раннем этапе. Именно там вылезают самые противные баги.

Таким образом :)

@tanstack/vue-table - это как спортивный автомобиль: круто, быстро, но если дорога разбитая - лучше взять внедорожник. Библиотека даёт контроль, но требует понимания её внутренней кухни.

Я не жалею, что попробовал. Теперь лучше понимаю, где стоит писать своё, а где можно взять готовое. Главное - не гнаться за модными решениями, а смотреть на задачу и команду.

Комментарии (0)