オトナのVue.js〜Nuxt.js入門

2018年4月16日

オトナのプログラミング勉強会@未来会議室

新垣圭祐

自己紹介

  • 新垣 圭祐(シンガキ・ケイスケ)
  • 30歳
  • フリーランス
  • Laravel / Vue.js

オトナのプログラミング勉強会

  • http://otona.pro
  • 2016年8月から開始
  • 月2回開催(第1,3水曜)
  • いつでも講師募集中
    • プログラム言語、機械学習、Web系…
  • YouTube Liveでリモート参加も振り返りも可

本日の内容

  • Vue.jsとは
    • jQueryと比べながらVue.js入門
  • Nuxt.jsとは
    • Nuxt.jsでアプリを作る

画像管理アプリを作る

  • サーバーから画像のリストを取得して表示
  • 画像をクリックすると拡大(モダル)で表示
  • いいねボタンをつくる

jQueryと比べながらVue.js入門

サーバーからデータを取得して表示(1)

  • jQueryの例(jquery.html)
<html>

<head>
    <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/bulma/0.7.0/css/bulma.css">
    <script src="https://code.jquery.com/jquery-3.3.1.min.js"></script>

</head>

<body>
    <div>
        <div id="images" class="columns">
        </div>
    </div>
    <script>
        (function() {
            var url = "https://dog.ceo/api/breed/hound/images";
            $.getJSON(url)
                .done(function(data) {
                    $.each(data.message.slice(0, 8), function(i, item) {
                        var html = "<img src='" + item + "' />";
                        $("<div class='column' />").html(html).appendTo("#images");
                    });
                });
        })();
    </script>
</body>

</html>

サーバーからデータを取得して表示(2)

  • Vue.jsの例(vue.html)
<html>

<head>
    <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/bulma/0.7.0/css/bulma.css">
    <script src="https://unpkg.com/vue"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/axios/0.18.0/axios.min.js"></script>
</head>

<body>
    <div id="app">
        <div class="columns">
            <div v-for="item in items" class='column'>
                <img v-bind:src='item' />
            </div>
        </div>
    </div>
    <script>
        new Vue({
            el: '#app',
            data: {
                items: []
            },
            mounted() {
                let self = this;
                let url = "https://dog.ceo/api/breed/hound/images";
                axios.get(url)
                    .then(function(response) {
                        self.items = response.data.message.slice(0, 8);
                    });
            }
        })
    </script>
</body>

</html>

サーバーからデータを取得して表示(3)

  • 表示(どちらも同じ)

サーバーからデータを取得して表示(4)

  • jQueryの例(jquery.html)
<div>
    <div id="images" class="columns">
    </div>
</div>
<script>
    (function() {
        var url = "https://dog.ceo/api/breed/hound/images";
        $.getJSON(url)
            .done(function(data) {
                $.each(data.message.slice(0, 8), function(i, item) {
                    var html = "<img src='" + item + "' />";
                    $("<div class='column' />").html(html).appendTo("#images");
                });
            });
    })();
</script>

APIで取得して、

imgタグを作って

IDで親(DOM)を取得して

ひっつける

サーバーからデータを取得して表示(5)

  • Vue.jsの例(vue.html)
<div id="app">
    <div class="columns">
        <div v-for="item in items" class='column'>
            <img v-bind:src='item' />
        </div>
    </div>
</div>
<script>
    new Vue({
        el: '#app',
        data: {
            items: []
        },
        mounted() {
            let self = this;
            let url = "https://dog.ceo/api/breed/hound/images";
            axios.get(url)
                .then(function(response) {
                    self.items = response.data.message.slice(0, 8);
                });
        }
    })
</script>

← APIからデータを取得

← データを保存

v-forディレクティブ

アイテムのリストを配列内のデータを使って表示

v-bindディレクティブ

要素の属性(attribute)を束縛(バインディング)する

サーバーからデータを取得して表示

  • jQueryもVue.jsもこの程度なら特に差はない

最新3件にNEWラベルを表示(1)

  • jQueryの例(jquery.html)
<div>
    <div id="images" class="columns">
    </div>
</div>
<script>
    (function() {
        var url = "https://dog.ceo/api/breed/hound/images";
        $.getJSON(url)
            .done(function(data) {
                $.each(data.message.slice(0, 8), function(i, item) {
                    var html = "<img src='" + item + "' />";
                    if (i < 3) {
                        html += '<span v-if="i < 3" class="tag is-danger">NEW</span>';
                    }
                    $("<div class='column' />").html(html).appendTo("#images");
                });
            });
    })();
</script>

表示(View)の修正であるにも関わらず、

ロジック部分を修正している

最新3件にNEWラベルを表示(2)

  • Vue.jsの例(vue.html)
<div id="app">
    <div class="columns">
        <div v-for="(item, i) in items" class='column'>
            <img v-bind:src='item' />
            <span v-if="i < 3" class="tag is-danger">NEW</span>
        </div>
    </div>
</div>

v-forのindexを

v-ifで判定してNEWラベルをつける

最新3件にNEWラベルを表示(3)

  • 表示の例

最新3件にNEWラベルを表示(4)

  • jQueryはロジック部分で動的にDOMを生成している
    • デザインや見た目の変更時にロジック部分を修正することになる
  • Vue.jsではディレクティブを使いView部分のみの修正で変更ができる

画像をクリックすると拡大して表示(1)

  • jQueryの例(jquery.html)
<div>
    <div id="images" class="columns">
    </div>
    <div id="modal" class="modal">
        <div class="modal-background"></div>
        <div class="modal-content"></div>
        <button class="modal-close is-large" aria-label="close" onclick="closeModal()"></button>
    </div>
</div>
<script>
    var items = []; //APIのレスポンス保持用の配列

    (function() {
        var url = "https://dog.ceo/api/breed/hound/images";
        $.getJSON(url)
            .done(function(data) {
                items = data.message.slice(0, 8);
               $.each(items, function(i, item) {
                    var html = "<img src='" + item + "' onclick='openModal(" + i + ")' />"; // イベントをフック

                    if (i < 3) {
                        html += '<span v-if="i < 3" class="tag is-danger">NEW</span>';
                    }
                    $("<div class='column' />").html(html).appendTo("#images");
                });
            });
    })();

    /**
        Modalのオープン
    **/
    function openModal(i) {
        $("#modal .modal-content").html("<p class='image'><img src='" + items[i] + "' /></p>");
        $("#modal").addClass("is-active");
    }

    /**
        Modalのクローズ
    **/
    function closeModal() {
        $("#modal").removeClass("is-active");
    }
</script>

画像をクリックすると拡大して表示(2)

  • jQueryの例(jquery.html)
items = data.message.slice(0, 8);
$.each(items, function(i, item) {
    var html = "<img src='" + item + "' onclick='openModal(" + i + ")' />"; 

    if (i < 3) {
        html += '<span v-if="i < 3" class="tag is-danger">NEW</span>';
    }
    $("<div class='column' />").html(html).appendTo("#images");
});

DOM生成時にイベントを記述

→どこでイベントが記述されているかわかりづらい

画像をクリックすると拡大して表示(3)

  • jQueryの例(jquery.html)
/**
    Modalのオープン
**/
function openModal(i) {
    $("#modal .modal-content").html("<p class='image'><img src='" + items[i] + "' /></p>");
    $("#modal").addClass("is-active");
}

/**
    Modalのクローズ
**/
function closeModal() {
    $("#modal").removeClass("is-active");
}

状態を保存しているのはDOMのみ

→ロジックを組みにくい(状態を取得したいと思ったらDOMから取らないといけない)

画像をクリックすると拡大して表示(4)

  • Vue.jsの例(vue.html)
<div id="app">
    <div class="columns">
        <div v-for="(item, i) in items" class='column'>
            <img v-bind:src='item' v-on:click="modal=i" />
            <span v-if="i < 3" class="tag is-danger">NEW</span>
        </div>
    </div>
    <div class="modal" v-bind:class="modal!==null ? 'is-active': ''">
        <div class="modal-background"></div>
        <div class="modal-content">
            <p class="image">
                <img :src="items[modal]" />
            </p>
        </div>
        <button class="modal-close is-large" aria-label="close" v-on:click="modal=null"></button>
    </div>
</div>
<script>
    new Vue({
        el: '#app',
        data: {
            items: [],
            modal: null,
        },
        mounted() {
            let self = this;
            let url = "https://dog.ceo/api/breed/hound/images";
            axios.get(url)
                .then(function(response) {
                    self.items = response.data.message.slice(0, 8);
                });
        }
    })
</script>

画像をクリックすると拡大して表示(4)

  • Vue.jsの例(vue.html)
<script>
    new Vue({
        el: '#app',
        data: {
            items: [],
            modal: null,
        },
        mounted() {
            let self = this;
            let url = "https://dog.ceo/api/breed/hound/images";
            axios.get(url)
                .then(function(response) {
                    self.items = response.data.message.slice(0, 8);
                });
        }
    })
</script>

← Modalで開いているitemを保持する変数を追加

画像をクリックすると拡大して表示(4)

  • Vue.jsの例(vue.html)
<div id="app">
    <div class="columns">
        <div v-for="(item, i) in items" class='column'>
            <img v-bind:src='item' v-on:click="modal=i" />
            <span v-if="i < 3" class="tag is-danger">NEW</span>
        </div>
    </div>
    <div class="modal" v-bind:class="modal!==null ? 'is-active': ''">
        <div class="modal-background"></div>
        <div class="modal-content">
            <p class="image">
                <img :src="items[modal]" />
            </p>
        </div>
        <button class="modal-close is-large" aria-label="close" v-on:click="modal=null"></button>
    </div>
</div>

← clickイベントを追加(modal変数にindexを代入)

↑ modalがnullでなければ表示

↑ itemsから画像URLを取得

↑ closeボタンでmodalをnullに戻す

状態はjsの変数で管理→リアクティブにViewに反映される

ディレクティブでイベントハンドリングを記述→見やすい

画像をクリックすると拡大して表示(5)

  • 表示例

いいね!ボタンを実装(1)

  • jQueryの例(jquery.html)
<div>
    <div id="images" class="columns">
    </div>
    <div id="modal" class="modal">
        <div class="modal-background"></div>
        <div class="modal-content"></div>
        <button class="modal-close is-large" aria-label="close" onclick="closeModal()"></button>
    </div>
</div>
<script>
    var items = []; //APIのレスポンス保持用の配列

    (function() {
        var url = "https://dog.ceo/api/breed/hound/images";
        $.getJSON(url)
            .done(function(data) {
                items = data.message.slice(0, 8).map(function(element) {
                    return {
                        url: element,
                        like: 0
                    };
                });
                $.each(items, function(i, item) {
                    var html = "<img src='" + item.url + "' onclick='openModal(" + i + ")' />"; // イベントをフック

                    // NEWラベルを追加
                    if (i < 3) {
                        html += '<span v-if="i < 3" class="tag is-danger">NEW</span>';
                    }

                    // いいねボタンを追加
                    html += " <a class='button is-warning  is-small' onclick='like( " + i + ")'><span>いいね! " + item.like + "件</span></a>";

                    $("<div id='item" + i + "' class='column' />").html(html).appendTo("#images");
                });
            });
    })();

    /**
        Modalのオープン
    **/
    function openModal(i) {
        console.log(items[i]);
        $("#modal .modal-content").html("<p class='image'><img src='" + items[i].url + "' /></p>");
        $("#modal").addClass("is-active");
    }

    /**
        Modalのクローズ
    **/
    function closeModal() {
        $("#modal").removeClass("is-active");
    }

    /**
        like!
    **/
    function like(i) {
        items[i].like += 1;
        $("#item" + i + " .button").html("<span>いいね! " + items[i].like + "件</span>");
    }
</script>

いいね!ボタンを実装(2)

  • jQueryの例(jquery.html)
(function() {
    var url = "https://dog.ceo/api/breed/hound/images";
    $.getJSON(url)
        .done(function(data) {
            items = data.message.slice(0, 8).map(function(element) {
                return {
                    url: element,
                    like: 0
                };
            });
            $.each(items, function(i, item) {
                var html = "<img src='" + item.url + "' onclick='openModal(" + i + ")' />"; // イベントをフック

                // NEWラベルを追加
                if (i < 3) {
                    html += '<span v-if="i < 3" class="tag is-danger">NEW</span>';
                }

                // いいねボタンを追加
                html += " <a class='button is-warning  is-small' onclick='like( " + i + ")'><span>いいね! " + item.like + "件</span></a>";

                $("<div id='item" + i + "' class='column' />").html(html).appendTo("#images");
            });
        });
})();

← likeカウント用に変数を定義

↓いいねボタンを表示&イベントハンドラを追加

↑DOMを取得するためにIDを追加

いいね!ボタンを実装(3)

  • jQueryの例(jquery.html)
/**
    like!
**/
function like(i) {
    items[i].like += 1;
    $("#item" + i + " .button").html("<span>いいね! " + items[i].like + "件</span>");
}

↓いいねボタン

DOM操作のためにIDを追加

表示部分&イベントフックをjs内に記述

状態がDOMのみに保存される(今回は変数をもたせた)

いいね!ボタンを実装(4)

  • Vue.jsの例(vue.html)
<div id="app">
    <div class="columns">
        <div v-for="(item, i) in items" class='column'>
            <img v-bind:src='item.url' v-on:click="modal=i" />
            <span v-if="i < 3" class="tag is-danger">NEW</span>
            <a class="button is-warning  is-small" v-on:click="item.like += 1">
                <span>いいね!{{item.like}}件</span>
            </a>
        </div>
    </div>
    <div class="modal" v-bind:class="modal!==null ? 'is-active': ''">
        <div class="modal-background"></div>
        <div class="modal-content">
            <p class="image">
                <img v-if="modal" :src="items[modal].url" />
            </p>
        </div>
        <button class="modal-close is-large" aria-label="close" v-on:click="modal=null"></button>
    </div>
</div>
<script>
    new Vue({
        el: '#app',
        data: {
            items: [],
            modal: null,
        },
        mounted() {
            let self = this;
            let url = "https://dog.ceo/api/breed/hound/images";
            axios.get(url)
                .then(function(response) {
                    self.items = response.data.message.slice(0, 8).map(function(element) {
                        return {
                            url: element,
                            like: 0
                        };
                    });
                });
        },
        methods: {
            like: function(i) {
                if (!this.likes[i] && parseInt(this.likes[i])) {
                    this.likes[i] = this.likes[i] + 1;
                } else {
                    this.likes[i] = 1;
                }
                console.log(Number(this.likes[i]));
            }
        }
    })
</script>

いいね!ボタンを実装(5)

  • Vue.jsの例(vue.html)
<div id="app">
    <div class="columns">
        <div v-for="(item, i) in items" class='column'>
            <img v-bind:src='item.url' v-on:click="modal=i" />
            <span v-if="i < 3" class="tag is-danger">NEW</span>
            <a class="button is-warning  is-small" v-on:click="item.like += 1">
                <span>いいね!{{item.like}}件</span>
            </a>
        </div>
    </div>
    <div class="modal" v-bind:class="modal!==null ? 'is-active': ''">
        <div class="modal-background"></div>
        <div class="modal-content">
            <p class="image">
                <img v-if="modal" :src="items[modal].url" />
            </p>
        </div>
        <button class="modal-close is-large" aria-label="close" v-on:click="modal=null"></button>
    </div>
</div>
<script>
    new Vue({
        el: '#app',
        data: {
            items: [],
            modal: null,
        },
        mounted() {
            let self = this;
            let url = "https://dog.ceo/api/breed/hound/images";
            axios.get(url)
                .then(function(response) {
                    self.items = response.data.message.slice(0, 8).map(function(element) {
                        return {
                            url: element,
                            like: 0
                        };
                    });
                });
        }
    })
</script>

いいね!ボタンを実装(6)

  • Vue.jsの例(vue.html)
mounted() {
    let self = this;
    let url = "https://dog.ceo/api/breed/hound/images";
    axios.get(url)
        .then(function(response) {
            self.items = response.data.message.slice(0, 8).map(function(element) {
                return {
                    url: element,
                    like: 0
                };
            });
        });
}

jQuery同様likeカウント用の変数を定義

いいね!ボタンを実装(7)

  • Vue.jsの例(vue.html)
<div id="app">
    <div class="columns">
        <div v-for="(item, i) in items" class='column'>
            <img v-bind:src='item.url' v-on:click="modal=i" />
            <span v-if="i < 3" class="tag is-danger">NEW</span>
            <a class="button is-warning  is-small" v-on:click="item.like += 1">
                <span>いいね!{{item.like}}件</span>
            </a>
        </div>
    </div>
    <div class="modal" v-bind:class="modal!==null ? 'is-active': ''">
        <div class="modal-background"></div>
        <div class="modal-content">
            <p class="image">
                <img v-if="modal" :src="items[modal].url" />
            </p>
        </div>
        <button class="modal-close is-large" aria-label="close" v-on:click="modal=null"></button>
    </div>
</div>

↑likeのカウントアップ

↑like数の表示

イベントハンドラわかりやすい

変数と表示が自動で同期(リアクティブ)

いいね!ボタンを実装(8)

  • 表示の例

jQueryと比べてのVue.js入門終わり

  • DOMで全てを解決するのは難しい
    • 状態をDOMに保存...
    • IDでDOMを取得...
    • JSでDOMを生成...
  • jQueryではUIとロジックが密結合しやすい
    • デザインを修正→JSを修正
    • ロジックを修正→JSを修正

Vue.jsで解決

ディレクティブ:Directive

  • v- から始まる特別な属性
  • 配下のプロパティや表現の値が変更されたら、Directiveのupdate() 関数が同期的に呼ばれる
<span v-if="seen">Now you see me</span>
<li v-for="todo in todos">
  {{ todo.text }}
</li>
<div v-bind:class="{ active: isActive }"></div>

v-if : 条件付きレンダリング

v-for : リストレンダリング

v-bind : 属性バインディング

<button v-on:click="greet">Greet</button>

v-on : DOMイベントの購読

Vue.jsの特長おさらい(1)

リアクティブ

  • js内の変数が更新されると自動でDOMが更新される
  • (今回やってませんが逆も更新→反映されます)

Vue.jsの特長おさらい(2)

data: {
    items: []
}
<div v-for="item in items" class='column'>
    <img v-bind:src='item' />
</div>

JSの中の変数

HTMLビュー(DOM)

Nuxt.js入門

Nuxt.jsとは何か

  • ユニバーサルな Vue.js アプリケーションを構築するためのフレームワーク

Vue-Router

  • Vueでルーティングをするためのライブラリ
  • コンポーネントとルートをマッピングする
const routes = [
  { path: '/foo', component: Foo },
  { path: '/bar', component: Bar }
]

const router = new VueRouter({
  routes
})

Nuxt.jsに入っているもの

Vuex

  • Vueで状態管理するためのライブラリ
  • 明示的にミューテーションをコミットすることによってのみ、ストアの状態を変更する
const store = new Vuex.Store({
  state: {
    count: 0
  },
  mutations: {
    increment (state) {
      state.count++
    }
  }
})

↑stateの値を変更するためには必ずmutationを通る

Nuxt.jsに入っているもの

Nuxt.jsのデプロイ

  • サーバーサイドレンダリング(SSR)
  • シングルページアプリケーション(SPA)
  • 静的ファイル生成

 Nuxt.js入門

(画像管理アプリ)

Dog API

The internet's biggest collection
of open source dog pictures.

https://dog.ceo/dog-api/

犬の画像がただで取得できます

Dog API

https://dog.ceo/dog-api/

犬の種別を取得(LIST ALL BREEDS)

種別毎の犬の画像を取得(BY BREED)

https://dog.ceo/api/breeds/list/all

https://dog.ceo/api/breed/hound/images

イヌコロ:完成予想図

種別一覧

画像一覧

いいね!ボタン

NEWラベル

プロジェクトの作成

$ vue init nuxt-community/starter-template inukoro
$ cd inukoro
$ npm install
$ npm run dev

ディレクトリ構成

.
├── README.md
├── assets
├── components
├── layouts
├── middleware
├── node_modules
├── nuxt.config.js
├── package-lock.json
├── package.json
├── pages
├── plugins
├── static
└── store

←Vueのコンポーネントを入れる場所

←Vueのページコンポーネントを入れる場所

←Nuxt.jsの設定ファイル

←Vuexストアのファイルを入れる

pagesディレクトリ

pages/
--| user/
-----| index.vue
-----| one.vue
--| index.vue
router: {
  routes: [
    {
      name: 'index',
      path: '/',
      component: 'pages/index.vue'
    },
    {
      name: 'user',
      path: '/user',
      component: 'pages/user/index.vue'
    },
    {
      name: 'user-one',
      path: '/user/one',
      component: 'pages/user/one.vue'
    }
  ]
}

ディレクトリ・ファイルを作成すると

自動でルーティングされる

pagesディレクトリ

pages/
--| _slug/
-----| comments.vue
-----| index.vue
--| users/
-----| _id.vue
--| index.vue
router: {
  routes: [
    {
      name: 'index',
      path: '/',
      component: 'pages/index.vue'
    },
    {
      name: 'users-id',
      path: '/users/:id?',
      component: 'pages/users/_id.vue'
    },
    {
      name: 'slug',
      path: '/:slug',
      component: 'pages/_slug/index.vue'
    },
    {
      name: 'slug-comments',
      path: '/:slug/comments',
      component: 'pages/_slug/comments.vue'
    }
  ]
}

動的なパラメータはアンダースコアで表現

storeディレクトリ

store/index.jsを作成し、

ストアインスタンスをエクスポートする

import Vuex from 'vuex'

const store = () => new Vuex.Store({

  state: {
    counter: 0
  },
  mutations: {
    increment (state) {
      state.counter++
    }
  }
})

export default store
<template>
  <button @click="$store.commit('increment')">{{ $store.state.counter }}</button>
</template>

Vueインスタンスでstoreが使えるようになる

アプリを開発していきます

まずはレイアウト

  1. bulma.cssを読み込む
  2. HTMLを修正

bulma.cssを読み込む

$ npm install @nuxtjs/bulma
module.exports = {
...
    modules: [
        '@nuxtjs/bulma'
    ],
...
}

npmでインストール

nuxt.config.jsを修正

<template>
  <div>
    <nav class="navbar">
      <div class="container">
        <div class="navbar-brand">
          <nuxt-link to="/breeds" class="navbar-item">イヌコロ</nuxt-link>
          <span class="navbar-burger burger" data-target="navbarMenu">
            <span></span>
            <span></span>
            <span></span>
          </span>
        </div>
      </div>
    </nav>
  </div>
</template>

pages/index.vueを修正

Nuxt.jsのビュー

.
├── README.md
├── assets
├── components
├── layouts
├── middleware
├── node_modules
├── nuxt.config.js
├── package-lock.json
├── package.json
├── pages
├── plugins
├── static
└── store

Nuxt.jsのレイアウト

<template>
  <div>
    <nav class="navbar">
      <div class="container">
        <div class="navbar-brand">
          <nuxt-link to="/breeds" class="navbar-item">イヌコロ</nuxt-link>
          <span class="navbar-burger burger" data-target="navbarMenu">
            <span></span>
            <span></span>
            <span></span>
          </span>
        </div>
      </div>
    </nav>
  </div>
</template>

pages/index.vue

<template>
    <nav class="navbar">
      <div class="container">
        <div class="navbar-brand">
          <nuxt-link to="/breeds" class="navbar-item">イヌコロ</nuxt-link>
          <span class="navbar-burger burger" data-target="navbarMenu">
            <span></span>
            <span></span>
            <span></span>
          </span>
        </div>
      </div>
    </nav>
</template>

components/AppHeader.vue

<template>
  <div>
    <AppHeader></AppHeader>
    <nuxt/>
  </div>
</template>

<script>
import AppHeader from '@/components/AppHeader.vue'
export default {
  components: {
    AppHeader
  }
}
</script>

layouts/default.vue

<template>
  <div></div>
</template>

pages/index.vue

犬種リストを表示

  1.  Dog APIから犬種リストを取得
  2. ストアに犬種リストを保存
  3. 犬種リストを表示

APIからのデータ取得

import axios from 'axios'

class DogApi {
    constructor() {
        this.apiBase = 'https://dog.ceo/api';
    }

    breeds() {
        return axios.get(`${this.apiBase}/breeds/list/all`)
            .then(json => {
                return json.data.message;
            })
            .catch(e => ({ error: e }));
    }
}

const dogApi = new DogApi();

export default dogApi;

api/dog.js

Dog API用にクラスを作成

$ npm install axios

Ajax用のライブラリをインストール

APIからのデータ取得

import Vuex from 'vuex'

const appStore = () => {
    return new Vuex.Store({
        state: {
            breed_list: {},
        },
        mutations: {
            breed_list_update(state, payload) {
                state.breed_list = {...payload}
            },
        }
    })
};

export default appStore

store/index.js

犬種リスト保存用のストアを作成

<template>
    <div></div>
</template>

<script>
    import dogApi from '@/api/dog'

    export default {
        async fetch({store}) {
            let json = await dogApi.breeds();
            store.commit('breed_list_update', json)
        },
    }
</script>

pages/index.js

pageコンポーネントから実行

APIからのデータ取得

データを取得→ストアに保存ができていることを確認

ページコンポーネントAPI

fetchメソッド

  • fetch メソッドは、コンポーネントがローディングされる前に毎回呼び出されます(ページコンポーネントに限り)
  • fetch メソッドは第一引数として コンテキスト を受け取り、コンテキストを使ってデータを取得してデータをストアに入れることができます。
export default {
    async fetch({store}) {
        let json = await dogApi.breeds();
        store.commit('breed_list_update', json)
    },
}

ストア

  • アプリケーションの状態(state)を保持するコンテナ
  • ストアの状態を直接変更することはできない。
    • ミューテーションをコミットすることによってのみ、ストアの状態を変更する
import Vuex from 'vuex'

const appStore = () => {
    return new Vuex.Store({
        state: {
            breed_list: {},
        },
        mutations: {
            breed_list_update(state, payload) {
                state.breed_list = {...payload}
            },
        }
    })
};

export default appStore

状態(state)

ミューテーション

犬種リストを表示する

<template>
    <section class="container">
        <div class="columns is-multiline">
            <div v-for="(item, i) in breed_list" v-bind:key='i' class='column is-2'>
                <a class="button">{{ i }}</a>
            </div>
        </div>
    </section>
</template>

<script>
    import dogApi from '@/api/dog'
    import { mapState } from 'vuex'

    export default {
        async fetch({store}) {
            let json = await dogApi.breeds();
            store.commit('breed_list_update', json)
        },
        computed: mapState(['breed_list']),
    }
</script>

pages/index.vue

VuxのmapStateヘルパーでstateを取得

v-forディレクティブで描画

犬一覧を取得

  1. 選択された犬種を取得
  2. 犬画像リストをAPIから取得
  3. 犬画像リストを描画

ルーティング

  • 犬種一覧(BREED LIST)

URL : /

  • 犬画像一覧(DOGS)

URL : /dogs/犬種名/

├── pages
│   ├── README.md
│   ├── dogs
│   │   └── _breed
│   │       └── index.vue
│   └── index.vue

ルーティング

<template>
    <div>
        test
    </div>
</template>

http://localhost:3000/dogs/test/

pages/dogs/_breed/index.vue

APIから犬画像リストを取得

class DogApi {
    ....
    dogs(breed) {
        return axios.get(`${this.apiBase}/breed/${breed}/images`)
            .then(json => {
                return json.data.message.map(function(element) {
                    return {
                        url: element,
                        like: 0
                    };
                });
            })
            .catch(e => ({ error: e }));
    }
    ...
}

api/dog.jsにメソッドを追加

import Vuex from 'vuex'

const appStore = () => {
    return new Vuex.Store({
        state: {
            breed_list: {},
            dog_list: [],
        },
        mutations: {
            breed_list_update(state, payload) {
                state.breed_list = {...payload}
            },
            dog_list_update(state, payload) {
                state.dog_list = [...payload]
            },
        }
    })
};

export default appStore

store/index.jsにstateとmutationを追加

APIから犬画像リストを取得

<template>
    <div>
        test
    </div>
</template>

<script>
    import dogApi from '@/api/dog'
    import { mapState } from 'vuex'

    export default {
        async fetch({store, params}) {
            let json = await dogApi.dogs(params.breed);
            store.commit('dog_list_update', json)
        },
        computed: mapState(['dog_list']),
    }
</script>

pages/dogs/_breed/index.vue

<div v-for="(item, i) in breed_list" v-bind:key='i' class='column is-2'>
    <nuxt-link :to="{ path: 'dogs/'+ i }" class="button">{{ i }}</nuxt-link>
</div>

pages/index.vueのリンクを修正

APIから犬画像リストを取得

データの取得を確認

犬画像リストを描画

pages/dogs/_breed/index.vue

<template>
    <section class="container">
        <div class="columns is-multiline">
            <div v-for="(item, i) in dog_list" v-bind:key='i' class='column is-1'>
                <img v-bind:src='item.url' />
            </div>
        </div>
    </section>
</template>

<script>
    import dogApi from '@/api/dog'
    import { mapState } from 'vuex'

    export default {
        async fetch({store, params}) {
            let json = await dogApi.dogs(params.breed);
            store.commit('dog_list_update', json)
        },
        computed: mapState(['dog_list']),
    }
</script>

Nuxt.jsおさらい

  • Vue.jsで開発する時に必要なものが入っており、設定が不要。
    • Vue-Router : ルーティング
    • Vuex : 状態管理
    • レイアウト 等々
  • デプロイ方法が多様。
    • サーバーサイドレンダリング
    • シングルページアプリケーション
    • 静的ファイル

オトナのVue.js〜Nuxt.js入門

By Keisuke Shingaki

オトナのVue.js〜Nuxt.js入門

  • 280
Loading comments...

More from Keisuke Shingaki