【Vue3】checkbox（チェックボックス） 〜 選択数制限 〜

「チェックボックスで選択できる数を3つまでに制限したい！」など、
チェックボックスの選択数を制限したい時の
Vue.js（Vue3 / Composition API）での記述方法をご紹介します。

# チェックボックスの選択数制限

こんな感じで動きます。
![checkbox_max_selection.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3472703/48d566cc-38af-dcbe-c605-47fbe8aeba5b.gif)

### コード完成版
```vuejs:MaxSelection.vue
<script setup>
import { ref } from 'vue'

// 補足1参照
const selectedOptions = ref([])

// 下記optionsは別ファイルに切り出すことをお勧めしますが、ここでは同じファイル内に記述します
const options = [
  { id: 1, value: '選択肢1' },
  { id: 2, value: '選択肢2' },
  { id: 3, value: '選択肢3' },
  { id: 4, value: '選択肢4' },
  { id: 5, value: '選択肢5' },
  { id: 6, value: '選択肢6' },
]

// ここでは最大選択数を3とします
const maxNumber = 3

// 補足2参照
const maxSelection1 = (index) => (
  selectedOptions.value.length >= maxNumber && !selectedOptions.value.includes(index)
)
// 別の記述方法
const maxSelection2 = (index) => (
  selectedOptions.value.length >= maxNumber && selectedOptions.value.indexOf(index) === -1
)

</script>

<template>
  <div class="margin">
    <div class="flex">
      <!-- 補足3参照 -->
      <div
        v-for="(option, index) in options"
        :key="option">
        <!-- 補足4参照 -->
        <label
          :for="`option_${index}`"
          :class="{ 'opacity-25' : maxSelection1(index) }">
        <input
          v-model="selectedOptions"
          :id="`option_${index}`"
          type="checkbox"
          :value="index"
          :disabled="maxSelection1(index)" />
          {{ option.value }}
        </label>
      </div>
    </div>
  </div>
</template>

<style>
/* cssは別ファイルからの読み込みを推奨しますが、ここでは同じファイル内に記述します */
.margin {
  margin: 2em;
}
.flex {
  display: flex;
  gap: 10px;
  margin: 5px 0;
}
.opacity-25 {
  opacity: 0.25;
}
</style>

```
***

### 各セクションの補足説明です。
#### - scriptタグ内 -

```javascript:補足1
import { ref } from 'vue'

const selectedOptions = ref([])
```
チェックボックスで選択された値を格納する配列"selectedOptions"を
リアクティブ（値が更新された時に変更が検知される状態）にするため、
ref関数を使用しています。

- 参考
  - [リアクティビティーの基礎](https://ja.vuejs.org/guide/essentials/reactivity-fundamentals)
***

```javascript:補足2
// ここでは最大選択数を3とします
const maxNumber = 3

const maxSelection1 = (index) => (
  selectedOptions.value.length >= maxNumber && !selectedOptions.value.includes(index)
)
// 別の記述方法
const maxSelection2 = (index) => (
  selectedOptions.value.length >= maxNumber && selectedOptions.value.indexOf(index) === -1
)
```

選択数制限判定の関数を2つ紹介しています。

1つ目の記述方法は、includesメソッドを使用する記述です。
maxSelection1関数では、以下2つの条件がどちらもtrueの場合にtrueを返します。
1. 選択された値が入っている配列（selectedOptions）の要素の数が`maxNumber`以上
2. 引数で渡ってきた`index`がselectedOptionsの要素に含まれていない（つまりチェックボックスが選択されていないということ）

※関数を実行している部分は、補足4で説明しているので、
　trueを返すことでどのような動きになっているかは、補足4を確認してください。
<br>

2つ目の記述方法は、indexOfメソッドを使用する記述です。
こちらは、1つ目の記述方法と条件としては同じですが、
"選択されていない"ことを判定する方法として、
indexOfメソッドの「引数に与えられた内容が配列に存在しない場合は -1 を返す」
という特徴を利用した記述になっています。

1、2どちらでも結果は同じですが、パフォーマンス比較だとincludesメソッドの方が高速、
コードの読みやすさからもincludesメソッドの方が個人的にはお勧めです。

ちなみに、scriptタグ内でrefは「.value」でアクセスすることが可能なので、
`selectedOptions.value`となっています。

- 参考
  - [Array:length](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Array/length)
  - [vue.jsのcheckbox(チェックボックス)の上限](https://www.ithands.dev/checkbox-for-vue/#vuejscheckbox-2)
  - [indexOf()とincludes()の違い【やらかし備忘録】](https://zenn.dev/azunasu/articles/f3f4ce6ab573ea)
  - [[JavaScript] Array.includes vs Array.indexOf パフォーマンス比較](https://codenote.net/posts/4786/)
  - [【Vue】refを使ってリアクティブなデータを扱う](https://qiita.com/yusuke1209kitamura/items/a1a24361b33691dd1818)
***

#### - templateタグ内 -

```vuejs:補足3
<div
  v-for="(option, index) in options"
  :key="option">
```
v-forを使って、options（選択肢の配列）からデータを取り出し、繰り返しで表示します。
今回、配列のindexも利用したいので、 2つ目のエイリアスを指定しています。

- 参考
  - [リストレンダリング](https://ja.vuejs.org/guide/essentials/list)
  - [v-forの使い方を分かりやすく解説](https://jp.skilled.yashio-corp.com/media/vue/6120/)
***

```vuejs:補足4
<label
  :for="`option_${index}`"
  :class="{ 'opacity-25' : maxSelection1(index) }">
<input
  v-model="selectedOptions"
  :id="`option_${index}`"
  type="checkbox"
  :value="index"
  :disabled="maxSelection1(index)" />
  {{ option.value }}
</label>
```
input要素にv-modelを利用することで、入力値とデータを同期することができます。
チェックした順にvalue属性の値が配列"selectedOptions"に追加されていきます。

- 参考
  - [フォーム入力バインディング](https://ja.vuejs.org/guide/essentials/forms)
<br>

そして、maxSelection1関数（選択数制限判定の関数）でチェックボックスの制御を実行しています。
1. 選択された値が入っている配列（selectedOptions）の要素の数が`maxNumber`以上
2. チェックボックスが選択されていない

上記2つの条件がtrueである場合、
label要素に`opacity: 0.25`のclass属性と、input要素にdisabled属性を追加します。
そうすることで、上限数に達した時点でチェックが入っていないチェックボックスは
選択できないように＆視覚的にも選択できないことが分かるようにしています。
（チェックが入っているボックスは押下できるようにすることで、チェックの解除、
　チェックの付け直しを実現しています）

- 参考
  - [opacity](https://developer.mozilla.org/ja/docs/Web/CSS/opacity)
  - [HTML 属性: disabled](https://developer.mozilla.org/ja/docs/Web/HTML/Attributes/disabled)
<br>

また、label要素とinput要素を明示的に関連付けるために、
input要素にid属性を、label要素にfor属性を追加しています。
（forとidの値は同じにします）

または、inputをlabelの入れ子にすることでも、関連づけることが可能です。
その場合、forおよびid属性は必要ありません。
（上記ではforおよびidを追加していますが、追加しなくても同じく動作します）

label要素とinput要素を関連付けることで、チェックボックス部分だけでなく、
関連するラベル（テキスト）部分をクリックしてもチェックが入るようになります。

- 参考
  - [\<label>: ラベル要素](https://developer.mozilla.org/ja/docs/Web/HTML/Element/label)
***

以上です！
読んでいただきありがとうございました^^