😸

Tanstack Table v8使ってみた

2024/11/20に公開

What is Tanstack Table?

https://tanstack.com/table/latest

ヘッドレスUIライブラリです。
特徴としては

  • ヘッドレスUIのため、スタイルは一切あたっていない。開発者が自由にスタイリングできます。
  • バンドルサイズが小さい。(10~15kb)
  • カスタマイズ性が高い。
  • フレームワークに依存せず使用可能。

使用してみた感じとにかく自由度が高いなと感じました。

私はTableのライブラリとしては

  • react-data-grid
  • Ant Design
  • vuetify

の使用経験があります

antdは直感的に操作できて個人的にはかなり好きなのですが、スタイルを変更する必要がある場合はantdのコンポーネント内で多重に階層されているdivの中からant-~というクラスを見つけ、そのクラスのcssプロパティを見てそのdivに当たるようにstyled-componentで上書きするみたいな処理が必要で、見た目を変えるにはかなり苦労しました。
ただし、無料で使えるコンポーネントがかなり多くドキュメントの例もすごく充実していて便利なhooksも生えているので素晴らしいライブラリだと感じています。

react-data-gridに関してはスタイルがあまりあたっていなく、シンプルな見た目になっていてカスタマイズしやすいのですが、とにかくドキュメントが薄く、コンポーネントに生えているpropsを理解するにはライブラリの中身を見に行く必要が何度もありました。
そのため、学習コストが高いです。

vuetifyは見た目もおしゃれで最高だが、自由度は低い。あとvueでしか使えない。

一方tanstack tableではヘッドレスUIのためスタイリングの自由度が高いです
さらにドキュメントにはかなりコード例があるため、非常に分かりやすいです。

なぜtanstack tableを使用したか

私は現在自分のエンジニアとしての自己紹介サイトを作成しています。
こちらはドラクエ風になっていて、「冒険を開始する」を押すとゲームとして遊べるようになっているので、ぜひとも見てくれたら嬉しいです。

https://my-dq-portfolio.vercel.app/profile

このサイトは自分が自社開発企業などに転職する機会があったときに自分がどういった実装ができるかちゃんと可視化しておきたいという目的、自分の上司になる人やその会社として自分がカルチャーマッチしそうか採用側に判断して欲しいという理由から作成しています。

採用前にできるだけ自分の情報をさらけ出すことでミスマッチを防げるかなと思っているため、このサイトを作りました。

このサイトに自分の履歴を表示するという機能追加をしたかったので、tanstack tableを使いました。

このサイトはドラクエ風のレイアウトデザインにしているため、スタイリングのあるUI コンポーネントライブラリはほぼ使用できません。

そして、このサイトはルーティングはtanstack router、データフェッチはtanstack queryを使用しています。

それならば全て tanstack構成にしてしまおうということでtanstack tableを使うことを選定しました。

自分の履歴をtanstack tableで表示する

使い方はほぼ公式のドキュメントそのままです。

https://tanstack.com/table/latest/docs/framework/react/examples/basic

インストール

npm install @tanstack/react-table

実装

ルートコンポーネント

src/routes/profile/index.lazy.tsx
import { createLazyFileRoute } from '@tanstack/react-router';
import Profile from './-components/Profile';
import Skills from './-components/Skills';
import AcademicTable from './-components/AcademicTable';

export const Route = createLazyFileRoute('/profile/')({
  component: ProfileContainer,
});

function ProfileContainer() {
  return (
    <div className="min-h-screen flex flex-col items-center justify-center">
      <Profile />
      <Skills />
      <h2 className="text-2xl font-semibold mt-8 text-center">
        Educational background
      </h2>
      <AcademicTable />
    </div>
  );
}

テーブルコンポーネント

src/routes/profile/-components/AcademicTable.tsx
import React from 'react';
import {
  createColumnHelper,
  flexRender,
  getCoreRowModel,
  useReactTable,
} from '@tanstack/react-table';

type Academic = {
  year: number;
  month: number;
  description: string;
};

const tableData: Academic[] = [
  {
    year: 1996,
    month: 6,
    description: `高知県南国市にて爆誕。
    小さい頃の写真を見るとグレイくらい目がでかい。`,
  },
  {
    year: 2000,
    month: 4,
    description: `高知県南国市十市幼稚園入園。
    スクーターを乗りこなす。`,
  },
  {
    year: 2003,
    month: 4,
    description: `高知県南国市十市小学校入学。
      毎朝7時半に学校に行きサッカーをする日々を過ごす。`,
  },
  {
    year: 2009,
    month: 3,
    description: `高知県南国市十市小学校卒業。
    この頃「焼きたて!! ジャぱん」にハマっており、将来の夢はパン屋さんだった。`,
  },
  {
    year: 2009,
    month: 4,
    description: `高知県高知市土佐中学校に入学。
      中学受験をして高知県の中では一番の自称進学校に入る。`,
  },
  {
    year: 2012,
    month: 3,
    description: `高知県高知市土佐中学校を卒業。
    中高一貫校なので、特に感動や苦労などなくそのまま卒業する。
    中学のサッカー部がかなりハードだったので、解放された喜びが溢れる。`,
  },
  {
    year: 2012,
    month: 4,
    description: `高知県高知市土佐高等学校に入学。
      高校に入りようやく勉強を始める。`,
  },
  {
    year: 2015,
    month: 3,
    description: `高知県高知市土佐高等学校を卒業。
      最後の学内の定期テストで学年3位を取る`,
  },
  {
    year: 2015,
    month: 4,
    description: `青山学院大学に入学。
    「東京への憧れ」ただこの一点のみの重大な理由で大学を選択。
    (渋谷すらこの時点では東京のどこにあるかあんまり分かっていなかった)`,
  },
  {
    year: 2019,
    month: 3,
    description: `青山学院大学を卒業。
    大学生時代はブレイクダンスにハマりひたすら回っていた。
    ここまで全てストレートに進んだ自分を褒めてあげたい。`,
  },
];

const columnHelper = createColumnHelper<Academic>();

const columns = [
  columnHelper.accessor('year', {
    header: '年',
    cell: (info) => info.getValue(),
  }),
  columnHelper.accessor('month', {
    header: '月',
    cell: (info) => info.getValue(),
  }),
  columnHelper.accessor('description', {
    header: '学歴',
    cell: (info) => info.getValue(),
  }),
];

const AcademicTable: React.FC = () => {
  const table = useReactTable({
    data: tableData,
    columns,
    getCoreRowModel: getCoreRowModel(),
  });

  return (
    <div className="mt-8 w-full max-w-4xl px-4">
      <table className="min-w-full border-collapse">
        <thead>
          {table.getHeaderGroups().map((headerGroup) => (
            <tr key={headerGroup.id} className="bg-gray-800">
              {headerGroup.headers.map((header) => (
                <th key={header.id} className="border px-4 py-2 text-left">
                  {header.isPlaceholder
                    ? null
                    : flexRender(
                        header.column.columnDef.header,
                        header.getContext()
                      )}
                </th>
              ))}
            </tr>
          ))}
        </thead>
        <tbody>
          {table.getRowModel().rows.map((row) => (
            <tr key={row.id} className="hover:bg-gray-800">
              {row.getVisibleCells().map((cell) => (
                <td key={cell.id} className="border px-4 py-2">
                  {flexRender(cell.column.columnDef.cell, cell.getContext())}
                </td>
              ))}
            </tr>
          ))}
        </tbody>
      </table>
    </div>
  );
};

export default AcademicTable;

ハマったことは一切なかったです!使いやすい!

dataとcolumnを定義する感じはantdに使用感が近いかなと感じました。

使い方

accessorでdata内の各項目の値を取得しているので、オブジェクトのキー名を合わせる必要があります。

フォーマッターも色々あって最高ですね!

cell : セルの書式設定に使用されます。
aggregatedCell : 集計時にセルをフォーマットするために使用されます。
header : ヘッダーの書式設定に使用されます。
footer : フッターの書式設定に使用されます。

ただ、jsx内は割とごちゃごちゃしてしまうのでここはある程度tanstack tableの学習が必要かなと感じました

この記事では全てのapiを紹介することはできないため、ここらへんで締めとさせていただきます

最後に

X(Twitter)もやってます!
この記事の感想、いいね、フォローなどしていただけるととっても励みになります!

https://x.com/dall_develop

よかったら作成したサイトも覗いていってください

https://my-dq-portfolio.vercel.app/

Happy Hacking!!!

Discussion