Vue.js(AtomicDesign)での連動するセレクトボックス

## 概要
連動するセレクトボックスを作成するにあたって、再利用可能なコンポーネントを使ったAtomicDesignに則って書かれた記事が見つかなかったのでこの記事を作成しました。

連動するセレクトボックスとは、以下のように1つ目のセレクトボックスの選択項目に伴って2つ目のセレクトボックスの選択肢が変わるものです。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2699390/f7a17dda-0ff3-7acd-86b5-ec552c517403.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2699390/578f68e4-61cf-b86d-9822-6d5479d902d8.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2699390/9afd1cb6-85b3-3750-d6dd-7d2ef2afb9a5.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2699390/d60f7c38-cc15-3749-77db-8cc42fdea134.png)

上記を実装するにあたって、AtomicDesignに則り以下のようなフォルダ構成でフロントエンド環境を構築しました。
```
C:.
├─assets
│  └─icon
├─components
│  ├─atoms
│  ├─molecules
│  ├─organisms
│  └─pages
└─router
```
.../atoms/AppSelectbox
.../molecules/DynamicSelectbox
.../pages/Parent
今回はこの3つのファイルでコンポーネント化した連動するセレクトボックスを表示させます。

以下からソースを子→親の順に記載し、私的なポイントを解説します。

### ソース

```vue:AppSelectbox.vue
<template>
  <select class="selection" v-model="inputedValue" @change="onChange">
    <option disabled hidden value="">{{ disabledLabel }}</option>
    <option v-for="option in options" :key="option.id" v-bind:value="option.id">
      {{ option.name }}
    </option>
  </select>
</template>

<script>
export default {
  name: "AppSelectBox",
  props: {
    value: {
      type: [String, Number],
      default: "",
    },
    options: {
      type: Array,
      required: true,
    },
    disabledLabel: {
      type: String,
      default: "",
    },
  },
  computed: {
    inputedValue: {
      get() {
        return this.value;
      },
      set(newValue) {
        this.$emit("input", newValue);
      },
    },
  },
  methods: {
    onChange: function (val) {
      this.$emit("selectedKey", val.target.value);
    },
  },
};
</script>

<style scoped>
.selection {
  width: 40%;
  padding: 0.5em;
  border: 1px solid #d0d0d0;
  appearance: menulist;
  -moz-appearance: menulist;
  -webkit-appearance: menulist;
}
</style>
```

上記が最も小さい単位のコンポーネントAppSelectboxです。
1つ目のセレクトボックスのキーが変更されたことをきっかけに2つ目のセレクトボックスの選択肢を変更する必要があるため、`@change='onChange'`とし、 methods内で親コンポーネントへ`"selectedKey"`という名前でemitしています。

また、[公式リファレンス](https://jp.vuejs.org/v2/guide/components.html#%E3%82%B3%E3%83%B3%E3%83%9D%E3%83%BC%E3%83%8D%E3%83%B3%E3%83%88%E3%81%A7-v-model-%E3%82%92%E4%BD%BF%E3%81%86 "コンポーネントで v-model を使う")に書いてあるv-modelの特性を応用し、親側で入力したvalueをgetし、inputの値をemitするために`v-model="inputedValue"`をcomputed内に置いています。

```vue:DynamicSelectbox.vue
<template>
    <div>
      <p>地方</p>
      <AppSelectBox :disabledLabel="catDisabledLabel" :options="catOptions" @selectedKey="$emit('selectedKey', $event)" v-model="firstValue"/>
      <p>県名</p>
      <AppSelectBox :disabledLabel="tagDisabledLabel" :options="tagOptions" v-model="secondValue"/>
    </div>
</template>

<script>
import AppSelectBox from '@/components/atoms/Input/AppSelectBox'

export default {
  name: 'DynamicSelectbox',
  components: {
    AppSelectBox
  },
  data () {
    return {
    }
  },
  props: {
    catDisabledLabel: {
      type: String,
      default: "",
    },
    tagDisabledLabel: {
      type: String,
      default: "",
    },
    catOptions: {
      type: Array,
      required: true,
    },
    tagOptions: {
      type: Array,
      required: true,
    },
    catValue:{
      required: true,
    },
    tagValue:{
      required: true,
    }
  },
  computed: {
    firstValue: {
      get() {
        return this.catValue;
      },
      set(newValue) {
        this.$emit("firstSelect", newValue);
      },
    },
    secondValue: {
      get() {
        return this.tagValue;
      },
      set(newValue) {
        this.$emit("secondSelect", newValue);
      },
    },
  },
}
</script>
```

そして次に、上記がAppSelectboxを2つ利用したDynamicSelectboxというコンポーネントです。こちらにAppSelectboxを2つ設置し、連動するセレクトボックスを作成します。
上述したように、1つ目のセレクトボックスのキーの変更を検知するために先ほどemitした`"selectedKey"`という名前をまた親コンポーネントへemitしています。（親側でデータを扱うため）
また、1つ目と2つ目のセレクトボックスでそれぞれvalueを取得したいので、それぞれ`"firstValue"`,`"secondValue"`という名前のv-modelを、こちらも先ほどと同じくcomputed内でそれぞれ`"firstSelect"`,`"secondSelect"`という名前で記述しています。


```vue:Parent.vue
<template>
    <DynamicSelectbox
        :catOptions="categories"
        :tagOptions="tags"
        :catValue="catValue"
        :tagValue="tagValue"
        @selectedKey="changeChoices"
        @firstSelect="catValue = $event"
        @secondSelect="tagValue = $event"
        :catDisabledLabel="catLabel"
        :tagDisabledLabel="tagLabel"
    />
</template>

<script>
import DynamicSelectbox from "@/components/molecules/DynamicSelectbox.vue";

export default {
  name: "Parent",
  data() {
    return {
      catLabel: "地方",
      tagLabel: "県名",
      catValue: "",
      tagValue: "",
      categories: [
          {id:1, name:"関東"},
          {id:2, name:"関西"},
          {id:3, name:"九州"},
      ],
      tags: [
          {}
      ],
    };
  },
  components: {
      DynamicSelectbox
  },
  methods: {
    changeChoices: function (selectedValue) {
        var tagsArray = [];
        if(selectedValue == 1) {
            tagsArray = [
                {id:1, name:"東京"},
                {id:2, name:"埼玉"},
                {id:3, name:"神奈川"},
            ]
        }else if(selectedValue == 2) {
            tagsArray = [
                {id:4, name:"大阪"},
                {id:5, name:"京都"},
                {id:6, name:"奈良"},
            ]
        }else if(selectedValue == 3) {
            tagsArray = [
                {id:7, name:"鹿児島"},
                {id:8, name:"福岡"},
                {id:9, name:"大分"},
            ]
        }
        this.tags = tagsArray;
    },
  },
};
</script>
```

これまで子コンポーネントから渡してきたものをpagesのParent.vueに表示します。
`@firstSelect`に代入されている$eventには、子コンポーネント（DynamicSelect）でemitされた際の第一引数が入り（ここではcatValue）、`@secondSelect`でも同様の理由でtagValueが入る。

また、AppSelectboxから`@change`→`@selectedKey`とemitされてきたものを親側で、`changeChoices`というメソッドとして使用しています。
このメソッド内で、1つ目のセレクトボックスで選択されたidに対する2つ目のセレクトボックスの選択肢がtagsというoptionsに代入されるようになっており、これによって題意を満たす動きが達成されます。

### まとめ
vue.jsの初学者である私にとって親子間のデータの受け渡しが特にわからず、苦労しました。
拙い記事ではありますが、同様に悩んでいる方の参考になれば幸いです。