---
title: "データテーブル"
description: "2,000 件のタスクを持つ Nuxt UI のテーブル。仮想化され、検索でき、行をグリップでドラッグして並べ替えます。"
canonical_url: "https://zizigy.github.io/vue-dnd-kit/ja/docs/examples/data-table"
---

> Index of every page on this site: [llms.txt](https://zizigy.github.io/vue-dnd-kit/llms.txt)

<example height="34rem" hint="タスクをグリップでドラッグしてみましょう。検索してからドラッグしても、隠れたタスクは元の位置のままです" name="ui-table" title="データテーブル">



</example>

Nuxt UI の `UTable` がテーブル、その行、その仮想化ライブラリを描画します：DOM にあるのは画面上の行だけです。Vue DnD Kit はそれを並べ替えます。テーブルは UI ライブラリが提供するそのままのもので、スロットも `ui` クラスもそのまま使っています。書き直した部分はありません。

## 注目ポイント

- **TaskTable.vue**：並べ替え可能なリストはテーブルの `<tbody>` で、`UTable` が公開する `tableRef` から取得します。テーブルはリストのドラフトを `data` として受け取るので、ドラッグ中に行が動きます。`get-row-id` はリストと同じく、タスクごとに行のキーを決めます。`axis: 'y'` はドラッグをその列の中に、`restrictToFirstScrollableAncestor` はテーブルの中に留めます：テーブルのスクロール要素が、行を囲む最初のスクロール可能な要素です。デモを展開すると、テーブルが画面いっぱいに広がります。
- **RowGrip.vue**：行を描画するのは `UTable` なので、あなたのコンポーネントが行になることはありません。最初のセルにあるグリップ（`#grip-cell` スロット）が、自分を囲む行をアイテムにし、そのハンドルになります。
- **検索**はリストの `filter` です：テーブルは一致するタスクを表示し、ドロップすると配列全体が書き戻され、隠れたタスクは元の位置に残ります。
- **tasks.ts**：2,000 件のタスクを `shallowRef` に入れています：ライブラリがその中身を見ることはありません。

## テーブルが描画する行

グリップは、行を返すゲッターを渡して `makeSortableItem` を呼び出します：描画された後の、グリップを囲む `<tr>` です。ハンドルなので、フォーカスと ARIA 属性はグリップが受け取り、行の残りの部分はクリックやテキストの選択に自由に使えます。

```ts [RowGrip.vue]
const grip = useTemplateRef<HTMLElement>('grip');
makeSortableItem(() => grip.value?.closest('tr') ?? null, {
  id: () => props.task.id,
  handle: grip,
  label: () => props.task.title,
});
```

```vue [TaskTable.vue]
<UTable ref="table" :data="items" :get-row-id="(task: Task) => task.id" …>
  <template #grip-cell="{ row }">
    <RowGrip :task="row.original" />
  </template>
</UTable>
```

オーバーレイはライブラリが作る行のコピーで、セルの幅を保った専用のテーブルに入っています。テーブルの行は影を描かないので、プロバイダーの `overlay-class` がオーバーレイの中身に影を付け、テーブルの背景とリングも加えます。行が移動している間は、行の `ui` クラスがその位置に色を付けます（`data-[dnd-placeholder]:bg-primary/5`）。

```vue [index.vue]
<DnDProvider overlay-class="overflow-hidden rounded-md bg-default shadow-xl ring ring-accented">
  <TaskTable />
</DnDProvider>
```

## 仮想化された行

`virtualize` はテーブルに TanStack Virtual を与えます：画面上の行とその周りの 8 行（`overscan`）が、2 つのスペーサー行の間に描画されます。

```vue [TaskTable.vue]
<UTable
  :data="items"
  :watch-options="{ deep: false }"
  :virtualize="{ estimateSize: 45, overscan: 8, onChange: () => captureLayout() }"
  class="min-h-0 flex-1"
/>
```

- `estimateSize` は最初の見積もりです：仮想化ライブラリは各行を描画するときに計測します。計測するとその下の行が動くので、`onChange` がリストの `captureLayout()` を呼び出します。こうしてドラッグ中は、その移動が一瞬で飛ぶのではなく、ライブラリによってアニメーションされます。
- テーブルがスクロール要素で、手に持った行はその中に留まります：上端や下端の近くではドラッグがテーブルをスクロールし、新しい行は見えてきたときに描画されます。
- ドラフトは移動のたびに新しい配列になるので、テーブルがその中身を監視する必要はありません（`deep: false`）。
- 持っている行から遠くまでスクロールすると、その行は描画範囲から外れてアンマウントされます。ドラッグは続きます：オーバーレイはまだその行を表示しており、行はドラフトが決めた位置に着地します。自分で作る仮想リストなら、`rangeExtractor` でその行をマウントしたままにできます（[仮想リスト](https://zizigy.github.io/docs/guides/virtual-lists#tanstack-virtual)を参照）。テーブルは行をスペーサー行の間に並べるので、ほかの行から離れて描画された行はそれらを押しのけてしまいます。そのため、このテーブルでは行をアンマウントさせています。

## 検索してからドラッグ

```ts [TaskTable.vue]
const { items, captureLayout } = makeSortableList(() => table.value?.tableRef?.tBodies[0] ?? null, {
  items: tasks,
  filter: (task) => {
    const text = query.value.trim().toLowerCase();
    return !text || `${task.id} ${task.title} ${task.assignee}`.toLowerCase().includes(text);
  },
  axis: 'y',
  modifiers: [restrictToFirstScrollableAncestor],
  label: 'Tasks',
});
```

「Mia」で検索し、彼女のタスクの 1 つを別のタスクの上へ移動してから、検索をクリアしてみましょう：彼女のタスクは新しい順序になり、ほかのタスクはすべて元の位置のままです。ドロップの `e.to.index` は表示されている行で数えます。`e.result` は配列全体を持っています。

<tip>

**ほかの UI ライブラリ**。Nuxt UI は素の要素を描画し、あなたのクラスと属性をそのまま渡します。その土台である Reka UI のプリミティブも同じです：必要なのは ref、スロット、クラスだけです。ほかのライブラリでもテーブルは自分で行を描画し、同じ手順が使えます：

- **行**。行全体を描画するスロット（Vuetify の `#item`、Quasar の `#body`）からは行の要素が得られます：それをアイテムにします。セルしか得られない場合（PrimeVue の `Column` の `#body`、Element Plus の列のデフォルトスロット、Naive UI の `render`）は、そのうちの 1 つにグリップを置き、ここと同じようにその `closest('tr')` をアイテムにします。
- **リスト**。テーブルのルート要素の中の `<tbody>` です：テーブルに付けたテンプレート参照でコンポーネントが、その `$el` で要素が得られます。
- **キーとデータ**。行のキーを自分の id にし（`item-value`、`row-key`、`dataKey`）、リストのドラフトをテーブルのデータとして渡します。
- **テーブル独自の並べ替えはオフ**。テーブル自身の行の並べ替えと列のソートは使いません：順序はリストのものです。
- **仮想テーブル**は行をスペーサーの間か、絶対配置のボックスに置きます：どちらでも動き、ドラッグは行がアンマウントされても続きます。

</tip>

<note>

StackBlitz ボタンは、Nuxt UI のドキュメントが Vue 向けに説明しているとおりに Nuxt UI を設定した Vite プロジェクト（`@nuxt/ui/vite` プラグイン、Tailwind CSS、サンプルを囲む `UApp`）でファイルを開きます。`@nuxt/ui` モジュールを入れた Nuxt アプリでは、フォルダーをコピーしてページで `index.vue` を描画してください。

</note>

## キーボードとスクリーンリーダー

Tab でタスクのグリップに移り、:kbd{value="Space"} を押して行を持ち上げます。:kbd{value="ArrowUp"} と <kbd value="ArrowDown">



</kbd>

 で動かし、:kbd{value="Space"} でドロップし、:kbd{value="Escape"} で元に戻します。行には `label` があり、リストにもあります：スクリーンリーダーが聞くのはキーではなく「Fix the login form dropped at position 3 of 2000 in Tasks.」です。

さらに読む：[仮想リスト](https://zizigy.github.io/docs/guides/virtual-lists) · [アイテムの一部を表示する](https://zizigy.github.io/docs/guides/sortable-lists#show-some-of-the-items) · [スタイリング：Tailwind と UI キット](https://zizigy.github.io/docs/guides/styling#tailwind-css) · [アクセシビリティ](https://zizigy.github.io/docs/guides/accessibility)

## Sitemap

See the full [sitemap](https://zizigy.github.io/vue-dnd-kit/sitemap.md) for all pages.
