この記事ではUIデザインに特化して解説します。デザインシステム・UI生成全般は デザインシステムとは?仕組み・構成要素・有名事例をエンジニア向けに完全解説 をご覧ください。

概要

TableProは、Excelライクな直感的なUIを備えたテーブル編集コンポーネントだ。Webアプリケーションで複雑なデータ表を扱う際の煩雑さ——フィルタリング、ソート、インライン編集、複数選択——を統一されたインターフェースで実現する。

GitHub上でスター数は着実に増加中。React・Vue・Vanillaなど主要なJavaScriptフレームワークに対応しており、SaaSプロダクトやエンタープライズダッシュボードでの採用が広がっている。スプレッドシートのような操作感を求めるプロダクトマネージャーや開発チームから重宝されているUIライブラリだ。

主な機能

  • セルレベルのインライン編集:テーブル内の任意のセルをダブルクリックすると即座に編集モードに切り替わり、データ型に応じた入力フォーム(テキスト・数値・選択肢・日付ピッカー)を自動生成する。

  • 高速ソート・フィルタリング:複数列による多段階ソートと、テキスト検索・範囲指定・カスタム述語を組み合わせたフィルタリングをメモリ効率よく処理し、数千行規模のデータセットでも遅延なく動作する。

  • 行・列の動的な追加削除:プログラマティックAPIにより、ユーザー操作またはコード側から行や列を動的に挿入・削除でき、スキーマの変更にリアルタイム対応できる。

  • キーボード操作とアクセシビリティ:TABキーによるセル間の移動、ENTERで編集確定、Ctrl+Cでコピー、Ctrl+Vでペーストなど、Excelと同等のキーボードショートカットに対応し、スクリーンリーダー互換性も実装されている。

  • 複数行選択とバルク操作:Shiftキー・Ctrlキーによる複数行選択に対応し、選択行に対する一括削除・データ更新・エクスポート操作を効率化する。

  • カスタムセルレンダラー:デフォルトのセルテンプレートのほか、任意のReactコンポーネント・Vue SFC・HTMLテンプレートをセルレンダラーとして登録でき、複雑なビジネスロジック(進捗バー、ステータスバッジ、アバター表示)を組み込める。

  • エクスポート機能:テーブルデータをCSV・JSON・Excelフォーマットでエクスポートでき、フィルタリング・ソート結果を反映した状態でダウンロードできる。

技術スタック

  • 対応フレームワーク:React 16.8+(Hooks対応)、Vue 2.6+ / Vue 3、Vanilla JavaScript(フレームワーク非依存)
  • ビルドツール:Webpack 5、Vite、Rollup
  • スタイリング:CSS-in-JS(Styled Components)、Tailwind CSS、SCSS
  • 型安全性:TypeScript 4.5+(型定義ファイル標準付属)
  • ピアデペンデンシー:React / Vue のみ(バンドルサイズ最小化)
  • テスト環境:Jest、React Testing Library、@vue/test-utils

導入方法

NPMを使ったインストールが標準的だ:

npm install tableproapp-tablepro
# または
yarn add tableproapp-tablepro

Reactでの基本的な使用例:

import { TablePro } from 'tableproapp-tablepro';

const columns = [
  { key: 'id', label: 'ID', editable: false },
  { key: 'name', label: '名前', type: 'text' },
  { key: 'email', label: 'メール', type: 'email' },
];

const data = [
  { id: 1, name: '田中太郎', email: '[email protected]' },
  { id: 2, name: '鈴木花子', email: '[email protected]' },
];

<TablePro columns={columns} data={data} />

Vue 3 Composition API + TypeScript での使い方

Vue 3でTypeScriptを使った実装例を示す。<script setup>構文とComposition APIで型安全に扱える:

<script setup lang="ts">
import { ref, computed } from 'vue'
import { TablePro } from 'tableproapp-tablepro'
import type { ColumnDef, TableRow, TableProInstance } from 'tableproapp-tablepro'

interface Product {
  id: number
  name: string
  price: number
  stock: number
  status: 'active' | 'inactive' | 'draft'
}

const tableRef = ref<TableProInstance | null>(null)

const columns: ColumnDef<Product>[] = [
  { key: 'id', label: 'ID', editable: false, width: 60 },
  { key: 'name', label: '商品名', type: 'text', sortable: true },
  { key: 'price', label: '価格', type: 'number', sortable: true,
    formatter: (val: number) => ${val.toLocaleString()}` },
  { key: 'stock', label: '在庫', type: 'number' },
  { key: 'status', label: 'ステータス', type: 'select',
    options: ['active', 'inactive', 'draft'] },
]

const data = ref<Product[]>([
  { id: 1, name: 'プレミアムプラン', price: 9800, stock: 999, status: 'active' },
  { id: 2, name: 'スタンダードプラン', price: 4800, stock: 999, status: 'active' },
  { id: 3, name: 'トライアルプラン', price: 0, stock: 999, status: 'draft' },
])

// 編集イベントハンドラ
const handleCellEdit = (row: TableRow<Product>, key: keyof Product, value: unknown) => {
  const index = data.value.findIndex(d => d.id === row.id)
  if (index !== -1) {
    data.value[index] = { ...data.value[index], [key]: value }
  }
}

// フィルタリングされた行数
const filteredCount = computed(() => tableRef.value?.getFilteredRows().length ?? 0)
</script>

<template>
  <div>
    <p>表示中: 件</p>
    <TablePro
      ref="tableRef"
      :columns="columns"
      :data="data"
      :editable="true"
      @cell-edit="handleCellEdit"
    />
  </div>
</template>

Vue 3の<script setup>構文との相性が良く、ref型推論によりセルデータの型安全性を維持できる。

カスタムセルレンダラー(Reactコンポーネント)

ビジネスロジックを含むセルを実装する際は、カスタムセルレンダラーが重要になる。以下はステータスバッジを表示するReactコンポーネントの例だ:

import React from 'react'
import { TablePro, CellRendererProps } from 'tableproapp-tablepro'

// ステータスバッジコンポーネント
const StatusBadge: React.FC<CellRendererProps<string>> = ({ value, row, onChange }) => {
  const colorMap: Record<string, string> = {
    active: '#22c55e',
    inactive: '#94a3b8',
    draft: '#f59e0b',
    error: '#ef4444',
  }

  const handleChange = (e: React.ChangeEvent<HTMLSelectElement>) => {
    onChange?.(e.target.value)
  }

  return (
    <select
      value={value}
      onChange={handleChange}
      style=
    >
      <option value="active">アクティブ</option>
      <option value="inactive">無効</option>
      <option value="draft">下書き</option>
      <option value="error">エラー</option>
    </select>
  )
}

// テーブルに登録
const columns = [
  { key: 'id', label: 'ID', editable: false },
  { key: 'name', label: '名前', type: 'text' },
  {
    key: 'status',
    label: 'ステータス',
    renderer: StatusBadge,  // カスタムレンダラーを登録
    editable: true,
  },
]

イベントハンドリングとAPIとの統合

実際のプロダクション環境では、セル編集のたびにAPIを呼び出して永続化する必要がある。以下はReact + fetch APIを使った統合例だ:

import { useCallback, useState } from 'react'
import { TablePro } from 'tableproapp-tablepro'
import type { TableRow, EditEvent } from 'tableproapp-tablepro'

function DataTable() {
  const [saving, setSaving] = useState<string | null>(null)
  const [error, setError] = useState<string | null>(null)

  // デバウンスつき保存処理
  const handleEdit = useCallback(async (event: EditEvent) => {
    const { rowId, key, newValue } = event
    setSaving(`${rowId}-${key}`)
    setError(null)

    try {
      const res = await fetch(`/api/records/${rowId}`, {
        method: 'PATCH',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ [key]: newValue }),
      })
      if (!res.ok) throw new Error(`HTTP ${res.status}`)
    } catch (err) {
      setError(`保存に失敗しました: ${err}`)
      // 楽観的更新の場合はここでロールバック処理
    } finally {
      setSaving(null)
    }
  }, [])

  return (
    <>
      {saving && <div className="saving-indicator">保存中...</div>}
      {error && <div className="error-message">{error}</div>}
      <TablePro
        columns={columns}
        data={data}
        onEdit={handleEdit}
        // 編集中セルにローディング表示
        loadingCells={saving ? [saving] : []}
      />
    </>
  )
}

コンポーネントアーキテクチャ

TableProの内部は4層で構成されており、各層が独立して動作する:

flowchart TD A["アプリケーション層
React / Vue / Vanilla"] --> B["TableProコア
状態管理・イベント処理"] B --> C1["データ層
ソート・フィルタ・ページネーション"] B --> C2["レンダリング層
仮想スクロール・セルレンダラー"] B --> C3["インタラクション層
キーボード・マウス・D&D"] C1 --> D["DOM出力
仮想化された行・列"] C2 --> D C3 --> D D --> E1["エクスポート
CSV / JSON / Excel"] D --> E2["外部API
onEdit / onSort / onFilter"]

仮想スクロール(Virtual Scroll)を採用しているため、数万行のデータを扱っても実際にDOMに描画されるのは表示領域内の行だけだ。これが大規模データセットでのパフォーマンスを支える核心技術となっている。

大規模データセットのパフォーマンス

TableProの仮想スクロールは、表示領域外の行をDOMから除外することで、10万行を超えるデータでも快適なスクロールを実現する。

パフォーマンスに関する主要な設計判断:

手法 効果 適用場面
仮想スクロール レンダリング行数を定数に固定 行数 > 1000
メモ化(React.memo) 不要な再レンダリング防止 頻繁な状態更新
クライアントサイドフィルタ APIコールなしで即時絞り込み 静的データ
サーバーサイドページネーション 初期ロード時間を短縮 動的・大規模DB

100万行を超える場合はサーバーサイドページネーションと組み合わせることが推奨される。TableProはその場合もonPageChangeonFilterChangeイベントでバックエンドAPIとの連携に対応している。

競合比較

項目 TablePro ag-Grid React Data Grid
主な強み Excelライク・シンプルAPI エンタープライズ機能・安定性 軽量・React統合
対応フレームワーク React / Vue / Vanilla React / Angular / Vue / Vanilla React専用
ライセンス MIT(オープンソース) Community / Commercial二層構成 MIT
バンドルサイズ 約45KB(gzip) 約200KB+(Community版) 約80KB
インライン編集 ネイティブ搭載・直感的 プラグイン実装で対応可 プラグイン実装で対応可
学習曲線 短い(APIシンプル) 中程度(オプション多数) 短い(React開発者向け)

TableProはシンプルで直感的なAPI設計が強みで、Excelに慣れた非エンジニアでも基本操作が容易だ。ag-Gridはエンタープライズグレードの豊富なプラグインと商用サポートを備える一方、ライセンス費用とセットアップの複雑さが課題になる。React Data Gridは軽量性に優れるがReact限定かつ高度なカスタマイズには向かない。

ag-Gridからの移行ガイド
ag-GridのColumnDef構造はTableProと概念的に近く、fieldkeyheaderNamelabelの置き換えが主な作業になる。ag-GridのcellRendererはTableProのrendererプロパティに対応する。ライセンス費用が発生しないCommunity版から移行する場合、バンドルサイズの削減(約200KB→45KB)と設定の簡略化が主なメリットだ。エンタープライズ版固有のピボットテーブルやサーバーサイドRow Modelを使っている場合は追加実装が必要になる。

こんな人におすすめ

  • 管理画面・ダッシュボード開発者:売上集計表、ユーザーリスト、在庫管理画面など、業務データを効率よく閲覧・編集する必要があるSaaSプロダクトのUIをシンプルに構築できる。Apache Airflowのようなワークフロー管理ツールのWebフロントエンドにも活用できる。

  • Excelの代替Webアプリを作りたい企業:スプレッドシートの使い勝手に慣れた利用者でも違和感なく移行でき、クラウドベースのデータ管理を実現できる。

  • スタートアップ・小〜中規模開発チーム:MITライセンスのため商用利用に制約がなく、ag-Gridなどの商用ライブラリと比較して総所有コストを削減できる。

  • 型安全なJavaScript開発を重視するチーム:TypeScript完全対応かつ詳細な型定義ファイルが付属するため、IDEの補完機能とコンパイル時の型チェックで実装ミスを防げる。

  • 複数フレームワーク間でコンポーネントを共有したい組織:Vanilla JavaScriptベースの設計により、React・Vueの両方で同じコンポーネントロジックを使い回せ、フレームワーク移行時の学習コストを最小化できる。

参照ソース