【Nuxt.js】アコーディオン（スライドトグル）を実装する

#■目的
**勉強のためNuxt.jsのサンプル集を作る**

##目標4
**アコーディオン（スライドトグル）を実装する**

##ざっくり仕様
* テンプレート内でのベタ書きパタンと配列読み込みの2パタンを実装する
* 上記2パタンは1つのコンポネントファイルで動作させる
* コンテンツ開閉時はアニメーションさせる
* コンテンツの高さに依存しない


##default.vue
ベースファイルは[ここ](https://qiita.com/MasayukiAbe/items/c4ed2d788fbd80b85506 "【Nuxt.js】ハンバーガーボタンとグロナビを実装する")で書いたものと同じ。
`components/template/Accordion.vue`内で処理をしていく。


##Accordion.vue（親）
まずはtemplate部分

```vue:components/template/Accordion.vue
<template>
  <main>
    <section id="p-accordion_box02">
      <div class="c-box">
        <!--
          ベタ書きパタン
          dt,ddの関係にしたければコンポネントをｄｌで括ればいい。
          aco-title-tagで指定するHTMLが開閉用のボタンになる
        -->
        <dl>
          <MoleculesEtcAccordion
            aco-title-tag="dt"
            aco-title="べたっと書く"
          >
            <dd>ここでは開閉ボタンをdt、展開要素をddに指定。この組み合わせが一番好きです。</dd>
          </MoleculesEtcAccordion>
        </dl>
        <MoleculesEtcAccordion
          aco-title-tag="h4"
          aco-title="ベタ書きだから自由"
        >
          <p>次は開閉ボタンをh4、展開要素を複数のタグで構成。</p>
          <p>たとえば画像を設置したり。class指定すればとにかく自由</p>
          <figure><img src="/images/sample/accordion.jpg" alt="アコーディオン" width="960" height="960"></figure>
        </MoleculesEtcAccordion>
      </div>
    </section><!-- /box02 -->

    <section id="p-accordion_box03">
      <div class="c-box">
        <!--
          配列読み込みパタン
          配列で処理するのだから自ずと流し込む型が決まってくる
        -->
        <ul>
          <li v-for="acoList in acoLists" :key="acoList.id" :class="acoList.id">
            <dl>
              <MoleculesEtcAccordion
                aco-title-tag="dt"
                :aco-title="acoList.acoArrayTitle"
                :aco-index="acoList.index"
              >
                <dd>{{ acoList.acoArrayBody }}</dd>
              </MoleculesEtcAccordion>
            </dl>
          </li>
        </ul>
      </div>
    </section><!-- /box03 -->
  </main>
</template>
```

次にscript部分。
とはいえ、配列を書いているだけです。

```vue:components/template/Accordion.vue
<script>
export default {
  computed: {
    acoLists () {
      const acoArrayList = [
        {
          id: 'aco1',
          acoArrayTitle: '配列から読み込む',
          acoArrayBody: '自由記述の代わりに配列から読み込む。マークアップはul>liでliを繰り返している。'
        },
        {
          id: 'aco2',
          acoArrayTitle: 'ただし……？',
          acoArrayBody: '配列で型にはめるからある程度制限されてしまう？　展開するコンテンツ内で自由に記述するにはどうすればいいんだろう。'
        },
        {
          id: 'aco3',
          acoArrayTitle: 'ちなみに',
          acoArrayBody: 'アコーディオンのコンポネントでは機能のみを設定しているので、見た目は読み込むページで自由に変えられる。'
        }
      ]
      return acoArrayList
    }
  }
}
</script>
```

どうでもいいけれど、配列内の`id`で`acoX`を指定して、それをそれぞれを
包括する要素の`class`に指定している。これにより、cssで可能な範囲内で
自由度を表現するバックアップ的な指定で、本来は必要ない。


##Accordion.vue（子）
親で読み込んでいるコンポネントのtemplate部分
なにも複雑なことはなく、`@click`と`v-if`を必要な箇所に
記述しているだけ。

```vue:components/molecules/etc/Accordion.vue
<template>
  <div class="c-accordion">
    <!--
      おなじみcomponentタグでacoTitleTagをpropsで受け取り
      親で指定したタグでボタン部分を括る
    -->
    <component
      :is="acoTitleTag"
    >
      <button class="c-accordion_btn" type="button" :class="{ 'active' : isOpen }" @click="acoToggle">
        <span>{{ acoTitle }}</span>
        <AtomsIconsArrowIcn />
      </button>
    </component>
    <!--
      アコーディオン部分をアニメーションさせるお作法transition
    -->
    <transition
      name="accordion"
      @before-enter="beforeEnter"
      @enter="enter"
      @before-leave="beforeLeave"
      @leave="leave"
    >
      <div v-if="isOpen" class="c-accordion_body" :class="{ 'active' : isOpen }">
        <div class="c-accordion_body--inner">
          <slot />
        </div>
      </div>
    </transition>
  </div>
</template>
```

script部分もシンプル。

```vue:components/molecules/etc/Accordion.vue
<script>
export default {
  props: { // propsで親からデータを受け取る
    acoTitleTag: {
      type: String,
      default: ''
    },
    acoTitle: {
      type: String,
      default: ''
    }
  },
  data () {
    return {　// 開閉フラグの初期値をfalseに
      isOpen: false
    }
  },
  methods: {
    acoToggle () { // @click="acoToggle"でクリックする度に開閉フラグを逆の値に
      this.isOpen = !this.isOpen
    },
    beforeEnter (el) { // leave(el)まではアコーディオンアニメーションのお作法で
      el.style.height = '0'
    },
    enter (el) {
      el.style.height = el.scrollHeight + 'px'
    },
    beforeLeave (el) {
      el.style.height = el.scrollHeight + 'px'
    },
    leave (el) {
      el.style.height = '0'
    }
  }
}
</script>
```

##結果
求めたいたアコーディオン機能が実装できた。
コンポネント分けすることで、1つクリックするとすべてのアコーディオンが開く、
といったようなこともない。