Edited at

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


はじめに

HTML、CSS、JavaScript まで学んで、初めてフロントエンドのフレームワークを学ぶ方向けチュートリアルです。

わたし自身もまだ学習を開始して間もないため備忘録として。


下準備

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


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:

スクリーンショット 2019-08-08 22.03.17.png

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


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ではNumberStringなどのプリミティブ値を使う必要があり、今回扱っているのはObject(連想配列)のためです。https://jp.vuejs.org/v2/guide/list.html?

このため今回はインデックスをv-bind:keyに指定しています。

:computer:ブラウザで確認:computer:

スクリーンショット 2019-08-08 22.06.33.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:

スクリーンショット 2019-08-08 22.08.38.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以下は見ていただくとわかるかと思いますが、その後、質問に対して自分が選んだ選択肢を配列にもたせます。

scriptdataに新たに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つのみ)のままです。


得点をカウントする

countdataで新たに定義

count: 0

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

③満点は何点なのか計算(questionslength分に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の連想配列について。「どう書かなければいけないのか」・「どう呼ばないと欲しいデータは取れないのか」慣れるまでエラーは沢山出ますがじっくりコードを見れば必ず書けるようになります(と信じてます)!