Vue入門15 買い物かご機能を独立させる

Vue入門の第15回(第1回はこちら)です。 今回は、前回までに作成してきた靴下販売ページの買い物かご機能を独立させて、ひとつに統一します。

前回までにサンプルとして靴下販売ページを作成してきましたが、商品情報を表示するitemコンポーネント内に買い物かご機能も含まれているため、商品ごとに別々の買い物かごが用意される作りになっていました。

今回は、買い物かご機能を独立させてひとつに統一することで、複数の商品を購入する際にもひとつの買い物かごでまとめて管理できるように変更します。

現時点のコードを確認

今回のコードの改変に取り掛かる前に、まずは前回までに作成したindex.html とmain.jsの現時点のコードを確認しておきましょう。

以下は、現時点のindex.htmlです。

<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <title>Vueサンプル</title>
    <script src="https://unpkg.com/vue"></script>
</head>
<body>
<div id="app">
    <item catchcopy="再入荷しました!" pointup="true"></item>
    <item></item>
    <item></item>
</div>
<script src="main.js"></script>
</body>
</html>

以下は、現時点のmain.jsです。

Vue.component('item', {
    props: {
        catchcopy: {
            type: String,
            default: '定番商品'
        },
        pointup: {
            type: Boolean,
            default: false,
            required: true
        }
    },
    template: `
        <div class="item">
            <div class="item-image">
                <img v-bind:src="itemImage" />
            </div>
    
            <div class="item-info">
                <h2>{{ itemFullName }}</h2>
                <ul><li v-for="itemInfo in itemInfos">{{ itemInfo }}</li></ul>
                <p>※{{ catchcopy }}</p>
                <p>※ポイント付与率:{{ pointRate }}%</p>
                <p v-if="zaiko > 2">在庫あり</p>
                <p v-else-if="zaiko <= 2 && zaiko > 0">残りわずか!</p>
                <p v-else>在庫なし</p>

                <div 
                v-for="itemColor in itemColors" 
                v-on:click="changeColor(itemColor.cImage, itemColor.cName)" 
                v-bind:style="{ backgroundColor: itemColor.cCode }" 
                style="width:30px; height:30px; margin-bottom:20px;"
                >
                </div>
    
                <button 
                v-on:click="addCart" 
                v-bind:disabled="soldout"
                >買い物かごの中身を増やす</button>
                <div class="cart">
                    <p>買い物かごの中身 {{ cart }}</p>
                </div>
            </div>
        </div>
    `,
    data() {
        return {
            itemName: 'キッズ靴下',
            itemColorName: 'ピンク',
            itemImage: './images/socks-pink.png',
            itemInfos: ["子供向け靴下", "コットン100%", "人気商品!"],
            itemColors: [
                {cName:"ピンク", cCode:"#ee6699", cImage:"./images/socks-pink.png"},
                {cName:"ブルー", cCode:"#33aaee", cImage:"./images/socks-blue.png"}
            ],
            zaiko: 5,
            cart: 0,
            soldout: false
        }
    },
    methods: {
        addCart: function () {
            this.cart += 1
            this.zaiko -= 1
            if (this.zaiko <= 0) {this.soldout = true}
        },
        changeColor (cImage, cName) {
            this.itemImage = cImage,
            this.itemColorName = cName
        }
    },
    computed: {
        itemFullName() {
            return this.itemName + '(' + this.itemColorName + ')'
        },
        pointRate() {
            if (this.pointup) {
                return 2
            } else {
                return 1
            }
        }
    }
})

var app = new Vue({
    el: '#app'
})

上記コードの実行結果は、以下のリンク先で確認できます。

https://programming-world.net/sample/vue_sample/14/index.html

現時点の買い物かご機能

買い物かごの表示部分は、templateプロパティのなかに記述されています。具体的には以下の部分です。

                <div class="cart">
                    <p>買い物かごの中身 {{ cart }}</p>
                </div>

また、買い物かごに商品を追加する処理を行う部分は、methodsプロパティのなかに記述されています。具体的には以下の部分です。

        addCart: function () {
            this.cart += 1
            this.zaiko -= 1
            if (this.zaiko <= 0) {this.soldout = true}
        },

現時点のコードでは、買い物かごがitemコンポーネントの一部になっているため、ひとつの商品にひとつの買い物かごが存在します。

それぞれの[買い物かごの中身を増やす]ボタンを押すと、商品ごとに生成される個別の買い物かごに保持されますが、この仕組みでは買い物かごの管理が複雑になってしまいます。

そこで、 買い物かご機能を独立させてひとつに統一することで、複数の商品を購入する際にもひとつの買い物かごでまとめて管理できるように変更します。

index.htmlを書き換える

それでは、今回のコード改変を始めていきましょう。HTMLファイル index.html を以下のように書き換えます。

<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <title>Vueサンプル</title>
    <script src="https://unpkg.com/vue"></script>
    <style>
    .item {width:30%; float:left;}
    .item-image img {width:100px;}
    </style>
</head>
<body>
<div id="app">
    <div class="cart">
        <p>買い物かごの中身 {{ cart.length }} {{ cart }}</p>
    </div>
    <item catchcopy="再入荷しました!" pointup="true" v-on:item-button-click="renderCart"></item>
    <item v-on:item-button-click="renderCart"></item>
    <item v-on:item-button-click="renderCart"></item>
</div>
<script src="main.js"></script>
</body>
</html>

今回のindex.htmlの書き換えでは、以下の4点が変更されています。

  1. レイアウト調整のために<style>~</style>でスタイルシートの指定を追加しています。スタイルシートは、買い物かご機能には関係ありません。見やすくするためだけのものです。
  2. <div class=”cart”>~</div>の買い物かごの中身を表示する部分が追加されています。この部分は前回まではmain.jsのtemplateプロパティのなかに記述していましたが、そこから切り出してindex.htmlに直接記述するように変更しました。
  3. 買い物かごの中身を表示する部分で {{ cart.length }} と {{ cart }} の2つのデータを表示するようにしています。今回のmain.jsの書き換えでは、 cart を単純な数値データから配列に変更するのですが、 {{ cart.length }} は配列の個数、 {{ cart }} で配列の内容となります。それぞれ、{{ cart.length }} は買い物かごに入った商品の総数を、 {{ cart }} は買い物かごに入った商品名を表示することになります。
  4. item要素に v-on:item-button-click=”renderCart” という指定を追加しています。v-onディレクティブでイベント発生時の動作を指定しています。具体的には、「item-button-click という名前のイベントが発動したら、 renderCartメソッドを実行しなさい」という指定になります。

main.jsを書き換える

JSファイルmain.jsを以下のように書き換えます。

Vue.component('item', {
    props: {
        catchcopy: {
            type: String,
            default: '定番商品'
        },
        pointup: {
            type: Boolean,
            default: false,
            required: true
        }
    },
    template: `
        <div class="item">
            <div class="item-image">
                <img v-bind:src="itemImage" />
            </div>
    
            <div class="item-info">
                <h2>{{ itemFullName }}</h2>
                <ul><li v-for="itemInfo in itemInfos">{{ itemInfo }}</li></ul>
                <p>※{{ catchcopy }}</p>
                <p>※ポイント付与率:{{ pointRate }}%</p>
                <p v-if="zaiko > 2">在庫あり</p>
                <p v-else-if="zaiko <= 2 && zaiko > 0">残りわずか!</p>
                <p v-else>在庫なし</p>

                <div 
                v-for="itemColor in itemColors" 
                v-on:click="changeColor(itemColor.cImage, itemColor.cName)" 
                v-bind:style="{ backgroundColor: itemColor.cCode }" 
                style="width:30px; height:30px; margin-bottom:20px;"
                >
                </div>
    
                <button 
                v-on:click="addCart" 
                v-bind:disabled="soldout"
                >買い物かごの中身を増やす</button>
            </div>
        </div>
    `,
    data() {
        return {
            itemName: 'キッズ靴下',
            itemColorName: 'ピンク',
            itemImage: './images/socks-pink.png',
            itemInfos: ["子供向け靴下", "コットン100%", "人気商品!"],
            itemColors: [
                {cName:"ピンク", cCode:"#ee6699", cImage:"./images/socks-pink.png"},
                {cName:"ブルー", cCode:"#33aaee", cImage:"./images/socks-blue.png"}
            ],
            zaiko: 5,
            soldout: false
        }
    },
    methods: {
        addCart: function () {
            this.$emit('item-button-click', this.itemName + this.itemColorName)
            this.zaiko -= 1
            if (this.zaiko <= 0) {this.soldout = true}
        },
        changeColor (cImage, cName) {
            this.itemImage = cImage,
            this.itemColorName = cName
        }
    },
    computed: {
        itemFullName() {
            return this.itemName + '(' + this.itemColorName + ')'
        },
        pointRate() {
            if (this.pointup) {
                return 2
            } else {
                return 1
            }
        }
    }
})

var app = new Vue({
    el: '#app',
    data: {
        cart: []
    },
    methods: {
        renderCart(itemDetail) {
            this.cart.push(itemDetail)
        }
    }
})

今回のmain.jsの書き換えでは、以下の3点が変更されています。

  1. itemコンポーネントのtemplateプロパティに記述していた <div class=”cart”>~</div>の買い物かごの中身を表示する部分が無くなっています。この部分はindex.htmlに移動して、HTMLソースに直接記述するように変更しました。
  2. itemコンポーネントのdataプロパティに記述していたcart: 0の部分を、Vueインスタンス側に移動して、 itemコンポーネントから独立させています。さらに、cart: [] と記述することで、cartを配列データとして定義しています。
  3. itemコンポーネントのaddCartメソッドに this.$emit(‘item-button-click’, this.itemName + this.itemColorName) の一行が追加されています。前回まではここで単純に買い物かごの中身を1増やす処理を実行していましたが、今回より item-button-clickイベントを実行するように指示する記述に変更しました。 this.itemName + this.itemColorName は 商品名と色を表すテキストですが 、このテキストを引数としてイベントに渡しています。
  4. Vueインスタンスに renderCart(itemDetail) {~}というメソッドが追加されています。これは item-button-clickイベントが起きた際に実行されるメソッドです。 renderCartメソッドでは、引数として受け取った商品名と色をthis.cart.push(itemDetail)の部分で配列cartに追加しています。

ここまでの説明だけでは一発で理解しづらいかもしれませんが、コードは後から見返すとしてとにかく表示を確認してみましょう。

表示を確認してみる

ここまで出来たら、index.htmlとmain.jsを上書き保存して表示を確認してみましょう。以下は、今回作成したサンプルページです。

https://programming-world.net/sample/vue_sample/15/index.html

index.htmlに買い物かごの中身を表示する部分を直接記述するように変更したことで、初期画面では上部に「買い物かごの中身 0 []」と表示されています。

[買い物かごの中身を増やす]ボタンを押した際に、以下の画面のように買い物かごの中身の表示が正しく変化していけば成功です。

前回と比べるとレイアウトが変更されて、3つの靴下商品が水平に並ぶようになっています。これはスタイルシートの指定によるものであり、買い物かご機能には関係ありません。

買い物かごに商品が入る流れを確認

あらためて買い物かごに商品が入る流れを確認してみましょう。

  1. ユーザーが靴下商品の色を選択して、[買い物かごの中身を増やす]ボタンをクリックします。
  2. すると、<button v-on:click=”addCart”>買い物かごの中身を増やす</button>の v-onディレクティブがクリックイベントを拾ってaddCartメソッドを呼び出します。
  3. addCartメソッド内では、this.$emit(‘item-button-click’, this.itemName + this.itemColorName)が実行されます。これは「this.itemName + this.itemColorNameを引数にして、item-button-click イベントを発動せよ」という指定になります。this.$emit(‘イベント名’)というのは書式として覚えてしまうとよいでしょう。this.$emit(‘イベント名’, 引数)で引数を渡すこともできます。今回引数として指定した this.itemName + this.itemColorName は、商品名と色です。
  4. item-button-clickイベントが発動すると、それに対応したitemコンポーネントの商品名と色を引数にして、renderCartメソッドが実行されます。
  5. renderCartメソッドでは、渡された商品名と色の情報をthis.cart.push(itemDetail)の部分で配列cartにpushします。pushするとは配列に追加するということです。
  6. 配列cartの情報が更新されると <p>買い物かごの中身 {{ cart.length }} {{ cart }}</p> の部分の表示がリアルタイムで更新されます。cart.length は配列cartの個数、つまり買い物かごに入っている商品の総数を表します。また、cartは配列の中身、つまり買い物かごに入っている商品の一覧を表します。

今回の変更はちょっとややこしいですが、買い物かごに商品が入る流れを大まかにつかんでから、上記のコードを見返すと理解しやすいのではないかと思います。

これで、買い物かご機能をitemコンポーネントから独立させて、ひとつに統一できました。ユーザーが複数の商品を購入する際にも、統一された買い物かごデータを1つだけフォーム送信すれば買い物が完了することになります。

◆ ◆ ◆

「Vue入門」シリーズは、今回でひと区切りとなります。Vueを使いこなすにはさらに学習や経験が必要ですが、おおまかにVueによるウェブアプリ作成がどのようなものかをつかむ基礎編はこれで修了です。