@tanstack/vue-table: почему я почти отказался от этого…

от автора

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

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

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

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


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

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

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


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

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

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

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

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

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

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


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

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

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

// useTableDataSync.tsimport { 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 — это как спортивный автомобиль: круто, быстро, но если дорога разбитая — лучше взять внедорожник. Библиотека даёт контроль, но требует понимания её внутренней кухни.

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

ссылка на оригинал статьи https://habr.com/ru/articles/1039302/