Vueのスロット入門から実践へ：Vuetifyのv-data-tableで関数付きスロットを活用する方法

# Vueのスロット（slot）を理解してVuetifyの関数付きスロット活用へつなげる実践例：`v-data-table`で行操作を柔軟にカスタマイズ

Vueには「スロット（slot）」という仕組みがあり、親コンポーネントが子コンポーネントにテンプレートを差し込むことで、柔軟なコンポーネント設計ができます。

この記事では、まず **Vueのスロットの基本構文と考え方**を解説し、その後、**VuetifyのUIコンポーネントでスロットを活用する実践例**として、`v-data-table`で関数をスロット経由で使って行操作する方法を紹介します。

---

## スロットとは？

スロットは、Vueで親コンポーネントが子コンポーネントに**テンプレート（HTML）やロジック（変数・関数）を渡すための仕組み**です。

### 例：デフォルトスロット

```vue
<!-- 子コンポーネント -->
<template>
  <div class="card">
    <slot></slot> <!-- 親からのコンテンツをここに挿入 -->
  </div>
</template>

<!-- 親コンポーネント -->
<MyCard>
  <p>これはカードの中身です。</p>
</MyCard>
```

---

## スコープ付きスロット

スロットに**値や関数を渡したい**場合、スコープ付きスロットを使います。

```vue
<!-- 子コンポーネント -->
<template>
  <slot :count="count" :increment="increment"></slot>
</template>

<script setup>
import { ref } from 'vue'
const count = ref(0)
const increment = () => { count.value++ }
</script>

<!-- 親コンポーネント -->
<MyCounter v-slot="{ count, increment }">
  <p>現在のカウント: {{ count }}</p>
  <button @click="increment">カウントアップ</button>
</MyCounter>
```

---

## Vuetifyのスロット活用へ

Vuetifyの多くのコンポーネントでも、スロットから関数や状態を受け取れる「スコープ付きスロット」が提供されています。

代表的な例が `v-data-table` の `item` スロットです。

---

## 実践：`v-data-table` でスロットから関数を受け取り、行を操作する

### 目標

* スロットから Vuetify が提供する関数（`isSelected`, `toggleSelect`）を受け取って行の選択状態を制御する
* 各行に「削除」ボタンを追加して独自の操作を実装する

### 実装例

```vue
<template>
  <v-data-table
    :items="desserts"
    item-value="name"
    show-select
    v-model:selected="selected"
  >
    <!-- itemスロット：各行のカスタム描画 -->
    <template #item="{ item, isSelected, toggleSelect }">
      <tr :style="{ backgroundColor: isSelected(item) ? '#f0f0f0' : '' }">
        <td>
          <v-btn icon @click="toggleSelect(item)">
            <v-icon>
              {{ isSelected(item) ? 'mdi-checkbox-marked' : 'mdi-checkbox-blank-outline' }}
            </v-icon>
          </v-btn>
        </td>
        <td>{{ item.name }}</td>
        <td>{{ item.calories }}</td>
        <td>
          <v-btn color="error" @click="removeItem(item)">削除</v-btn>
        </td>
      </tr>
    </template>
  </v-data-table>
</template>

<script setup>
import { ref } from 'vue'

const selected = ref([])

const desserts = ref([
  { name: 'プリン', calories: 300 },
  { name: 'アイスクリーム', calories: 200 },
  { name: 'ケーキ', calories: 400 },
])

const removeItem = (item) => {
  desserts.value = desserts.value.filter(d => d.name !== item.name)
}
</script>
```

---

## #は何の略？（v-slotの省略記法）

Vueでは、スロットを使うときに `v-slot:` というディレクティブを指定できます。たとえば：

```vue
<template v-slot:item>
  カスタム行
</template>

```

この `v-slot:` は、Vue 2.6 以降で導入された構文で、以下のように `#` に省略して書くことができます：

```vue

<template #item>
  カスタム行
</template>

```

Vuetifyのドキュメントではスロット名が `item.data-table-expand` のように書かれていますが、Vueテンプレート内では `#item.data-table-expand` のように使用します。

---

## Vuetifyのドキュメントからの読み解き方

Vuetifyの公式ドキュメントに記載されているスロット名（例：`item.${string}` や `header.${string}`）は、**そのままVueテンプレートの`#item.カラム名` のように変換して使えます**。

### 例：

* `item.${string}` → `<template #item.name>` や `<template #item.calories>` のように使用（カラム名に対応）
* `item.data-table-expand` → `<template #item.data-table-expand>`
* `header.${string}` → `<template #header.name>`

つまり、`item.${string}` や `header.${string}` のような表記は、**カラム名ごとのセル/ヘッダーのカスタマイズが可能**という意味です。

このように、Vuetifyのドキュメントを見ながら、スロット名に `#` をつけ、`${string}` の部分を実際のカラムキーに置き換えるだけで、目的のカスタマイズができるようになります。

---

## まとめ

* Vueのスロットは、テンプレートの差し込みだけでなく**値や関数も親に渡せる**強力な仕組み
* Vuetifyでは、多くのコンポーネントが**スコープ付きスロットを通じて関数や状態を提供**している
* ドキュメントのスロット名（例：`item.${string}`）に `#` をつけて `${string}` を実際のカラムキーに置き換えれば Vueテンプレートで使用可能
* `v-data-table` の `item`, `item.${string}`, `header.${string}` などは、行やセル単位での柔軟な描画カスタマイズが可能

---

## 参考リンク

* [Vue公式：スロットの使い方](https://ja.vuejs.org/guide/components/slots.html)
* [Vuetify v-data-table APIドキュメント（スロット）](https://vuetifyjs.com/ja/api/v-data-table/#slots-item)