Vue初心者が三択クイズをつくってみた<v-for, v-if, computed, methods>基礎の基礎

##はじめに

HTML、CSS、JavaScript まで学んで、初めてフロントエンドのフレームワークを学ぶ方向けチュートリアルです。
わたし自身もまだ学習を開始して間もないため備忘録として。

##下準備
今回はVue.js公式ホームページに載っているCDNを使用。そのため`html`シートに記述していきます。`head`に`script`を貼り付け、`body`の`div`中に`id="app"`と宣言。`script`の`data: {}`中に使用するクイズを用意します。

``````index.html
<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
  <title>チュートリアル</title>
</head>
<body>
  <div id="app">
    <p>{{ questions }}</p>
  </div>
  <script>
  const vm = new Vue({
    el: "#app",
    data: {
      questions: [
        {
          question: "ダチョウの脳みその重さは？",
          selections: [
            {
              "choice": "40g",
              "correct": true
            },
            {
              "choice": "400g",
              "correct": false
            },
            {
              "choice": "1400g",
              "correct": false
            },
          ],
        },
        {
          question: "次のうち生まれたのサイズが一番小さい動物は？",
          selections: [
            {
              "choice": "トイプードル",
              "correct": false
            },
            {
              "choice": "カンガルー",
              "correct": true
            },
            {
              "choice": "キリン",
              "correct": false
            },
          ],
        },
        {
          question: "次のうち本当にある恐竜の名前は？",
          selections: [
            {
              "choice": "ゴジラサウルス",
              "correct": true
            },
            {
              "choice": "デジモンサウルス",
              "correct": false
            },
            {
              "choice": "ポケモンサウルス",
              "correct": false
            },
          ],
        }
      ],
    }
  })
  </script>
</body>
</html>
```
ここでは選択肢は連想配列にしており、回答も `true` or `false` としています。
こうすることで後々管理が楽になります。

`<p>{{ questions }}</p>` と `body`タグの始めのほうに入れています。これでデータがちゃんと取れているのか確認してみます。

:computer:ブラウザで確認:computer:
<img width="928" alt="スクリーンショット 2019-08-08 22.03.17.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/296366/15273cc1-c8fb-6cf3-08da-ed73f1dddcc6.png">

となったので、`quesｔions` の中身が 連想配列で全て取れているのが確認できます。

## v-for でクイズを複数生成する

さて、クイズにしていくためにはまず質問のみを表示していく必要があります。
今回用意したクイズは合計3問。`v-for`を使って生成します。

```index.html
<div id="app">
    <div v-for="(item, questionIndex) in questions"
      v-bind:key=questionIndex
    >
      <p>{{ item.question }}</p>
    </div>
  </div>

```

`questions` の中身をひとつずつ見ていく（ループさせる）には`in`を使ったお決まりのフレーズがあります。引数を2つ、インデックスまで持たせているのは、インデックスを後で使うのもありますが、生成時に一意なキーを各要素に与える`v-bind:key`では`Number`や`String`などのプリミティブ値を使う必要があり、今回扱っているのは`Object`（連想配列）のためです。https://jp.vuejs.org/v2/guide/list.html?
このため今回はインデックスを`v-bind:key`に指定しています。

:computer:ブラウザで確認:computer:
<img width="711" alt="スクリーンショット 2019-08-08 22.06.33.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/296366/3e83edd7-9c46-fbf2-c365-d9024d439b6f.png">

これでオッケーです。`question` のみ表示できました。

## v-for で選択肢を複数生成する
3択問題なので選択肢も`v-for`で生成します。

```index.html
<div id="app">
    <div v-for="(item, questionIndex) in questions"
      v-bind:key=questionIndex
    >
      <p>{{ item.question }}</p>
      <ul>
        <li v-for="(selection, selectIndex) in item.selections"
          v-bind:key=selectIndex
        >{{ selection.choice }}</li>
      </ul>
    </div>
  </div>
```
ここで重要になってくるのは、「何を指し示しているのか」ということ。JavaScriptでも連想配列・配列に詰まった（ている）私ですが、コードをよく見てみます。
まず、`v-for` 以下は`question`の時と同様ですが、`in item.selections` に注目。`item` とは上記にもある`questions`の配列の内側にある1つの連想配列です。その中にある`selections` を指したいので`in item.selections` とします。
その1つの`selection`が持つ`choice`を表示するため`{{ selection.choice }}`としています。

:computer:ブラウザで確認:computer:
<img width="490" alt="スクリーンショット 2019-08-08 22.08.38.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/296366/d8017e41-1c44-01db-d3c8-4b0090f08e5c.png">

選択肢が表示されました。（スタイルは当てていません。）

## v-on `@click`を設定

クリックした後の処理を書いていきます。

```index.html
<li v-for="(selection, selectIndex) in item.selections"
    v-bind:key=selectIndex
    @click="clickSelection(questionIndex, selectIndex)"
>
 {{ selection.choice }}
</li>
```

```index.html
 
  <script>
  const vm = new Vue({
    el: "#app",
    data: {
      questions: [
        -省略-
      ],
      answerExistList: [],
    },
    methods: {
      clickSelection(questionIndex, selectIndex) {
        const clickedQuestion = this.questions[questionIndex]//クリックした質問
        const clickedSelection = clickedQuestion.selections[selectIndex]//クリックした選択肢
        const getClickedCorrect = clickedSelection.correct //クリックした選択肢の回答
        // this.$set(this.object, propertyName, value) 👉this.$setについて
        this.$set(this.answerExistList, questionIndex, selectIndex + '')
      }
    }
  })
  </script>
```

クリックイベント`@click`にメソッド名を入れて必要となる引数をもたせます。（questionIndex, selectIndex）
`script`内の`methods:`下にメソッドを書きます。クリックイベントで発火させる関数が引数をもつ場合は必ず`methods:`に書きましょう。`const`以下は見ていただくとわかるかと思いますが、その後、質問に対して自分が選んだ選択肢を配列にもたせます。

`script`の`data`に新たに`answerExistList: []`という配列を定義し、`methods:`で処理を書きます。

`data`に定義したものをメソッドで使う場合は必ず頭に`this`と付けます。そうする事で初めて利用できるようになります。

## this.$set を使う
:speech_balloon:　**ポイント**
プロパティを追加する際にうまくいかなかったので`this.$set`を使いました。https://jp.vuejs.org/v2/guide/reactivity.html
今回はインデックスの指定でエラーが出たので文字列にしています。
確認のため`html`内に`{{ answerExistList }}`と書いてみます。


:computer:ブラウザで確認:computer:
`[ 数字, 数字, 数字 ]`　と、何度クリックしてもこの配列がもつ要素は3つ（つまり質問3つに対して回答は3つのみ）のままです。

##得点をカウントする

① `count`を`data`で新たに定義
`count: 0`

②先程のクリックイベントに処理を追記。正解１回につき10点

③満点は何点なのか計算（`questions`の`length`分に10掛ける。`computed:`に記述）して表示

④一度クリックしたら再度同じ問題をクリックできないよう`return`する

```index.html
<div id="app">
    <p>{{ count }}点/{{ fullScore }}点中</p>
    <p>{{ answerExistList }}</p>
```

```index.html
 
  <script>
  const vm = new Vue({
    el: "#app",
    data: {
      questions: [
        -省略-
      ],
      answerExistList: [],
      count: 0👈
    },
    computed: {
      fullScore() {
        return this.questions.length * 10
      },
    methods: {
      clickSelection(questionIndex, selectIndex) {
        if(this.answerExistList[questionIndex]) {
          return
        }
        -省略-
        const getClickedCorrect = clickedSelection.correct //クリックした選択肢の回答
        // 回答が正解つまり　true なら 10点加算
        if(getClickedCorrect) {
          this.count += 10
        } else {
          this.count += 0
        }
      }
    }
  })
  </script>
```

`html`に`{{ fullScore }}`を書く時の注意点としては、これは関数ですが引数をもたない`computed:`の内容なので`()`は不要です。

あとは、`{{ answerExistList }}`はブラウザで表示しなくて良いので削除もしくはコメントアウトしておけば完成です:clap:

##＜追記＞　正解・不正解の表示

正解・不正解の表示について記述していなかったので追記です。　クリックした`question`の中の`selections`の中のインデックスについて、`answerExistList`に入っているインデックス番号を参照しています。三項演算子で true つまり正解であれば「正解」、そうでなければ「不正解」と返します。`v-if`では`answerExistList[questionIndex]`があれば表示としています。

```index.html
<div id="app">
    -省略-
   <p v-if="answerExistList[questionIndex]">
    {{ getJudgeText(questionIndex) }}
   </p>
```

```index.html
 
 <script>
   methods: {
      // ここに追記
      getJudgeText(questionIndex) {
        const question = this.questions[questionIndex]
        const answerExist = this.answerExistList[questionIndex]
        const selection = question.selections[answerExist]
        return selection.correct ? '正解' : '不正解'
      },
 </script>
```

##まとめ

Vue.js独特の書き方と、JavaScriptの連想配列について。「どう書かなければいけないのか」・「どう呼ばないと欲しいデータは取れないのか」慣れるまでエラーは沢山出ますがじっくりコードを見れば必ず書けるようになります（と信じてます）！