Nuxt.js で簡単な画像一覧アプリを作成する – Part.1

オトナのプログラミング勉強会について

オトナのプログラミング勉強会」は、熊本で開催しているプログラミングの勉強会です。 基本的に月2回(第1水曜日、第2水曜日)開催となります(2018/05/03現在)。

熊本のコワーキングスペース「未来会議室」共催のオープンでやっている勉強会ですので、参加は完全無料です。(プログラムを書く人を増やす、繋げることが目的なので、誰でも参加できるというスタンスです)

今回は、2018年4月18日に開催された「オトナのLaravel&Vue.js開発入門@未来会議室」勉強会の参加レポートです。今回はNuxt.jsとDog APIを使って犬種ごとに画像表示するWebアプリの作成を行いました。

主催者の方から内容掲載の許可をいただきましたのでブログにまとめておきます。

Nuxt.js とは

Nuxt.js は Vue アプリケーションを作成するフレームワークです。ユニバーサルアプリケーション、静的に生成されるアプリケーション、シングルページアプリケーションの中から作成するアプリケーションを選ぶことができます。

はじめに – Nuxt.js

Nuxt.js はコンポーネントとルートをマッピングするためのvue-routerや、Vue.jsで状態管理するためのライブラリVuexを内包しています。

Nuxt.jsの利用シーンについて

Nuxt.jsの主な機能は以下のようになっています。

Vue ファイルで記述できること(*.vue
コードを自動的に分割すること
サーバーサイドレンダリング
非同期データをハンドリングするパワフルなルーティング
静的ファイルの配信
ES2015+ のトランスパイレーション
JS と CSS のバンドル及びミニファイ化
<head> 要素(<title><meta> など)の管理
開発モードにおけるホットリローディング
プリプロセッサ: Sass, Less, Stylus など
HTTP/2 push headers ready
モジュール構造で拡張できること

はじめに – Nuxt.js

参考記事: Nuxt.jsを使うときに、SPA・SSR・静的化のどれがいいか迷ったら – Qiita

機能要件

  • サーバーから画像のリストを取得して表示
  • 画像をクリックするとモーダルウィンドウで拡大表示
  • 最新画像3件には「NEW!」ラベルを表示
  • いいねボタン(クリックでカウントアップ)を表示

最終的な画面イメージは以下のようになります。

01. 犬種の一覧を表示した状態
02. 犬種をクリックしたあとの画像表示

githubに  リポジトリ が公開されているので参考にしてください。

環境準備

動作確認環境は以下のとおりです。

  • Node.js (v8.9.1)
  • npm (5.7.1)
  • vue-cli (2.9.3)

Nuxt.jsでプロジェクトを作成するためには、開発環境にvue-cliをインストールしておく必要があります。

$ sudo npm install -g vue-cli
$ vue --version # 2.9.3

プロジェクトの作成

 スターターテンプレート があるので今回はこちらを使用します。プロジェクト名は「inukoro(イヌコロ)」です。犬の蔑称らしいですが特に意味はありません。

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

npm run devでビルドが完了すると http://localhost:3000 を開くように指示されます。次のようなWelcomeページが表示されたらOKです。

ディレクトリ構成について

重要なディレクトリやファイルは以下のとおりです。公式ガイドのビューに関する図解 も併せて確認してください。

pagesディレクトリ

開発に入る前に、ディレクトリについて説明をしておきます。pagesディレクトリは、Vueのページコンポーネントを格納するディレクトリです。トップページ(index.html)を Nuxt.js で表現する場合、「pages/index.vue」を配置するイメージでOKです。

ここで注目したいのは、pagesディレクトリに対して新規ディレクトリや .vue ファイルを作成すると、Nuxt.js が以下のように自動的にルーティングを切ってくれることです。非常に便利な機能です。

storeディレクトリ

次にstoreディレクトリの説明です。storeディレクトリではVuexを使用して状態管理を行います。Vuexでは明示的にミューテーションをコミットすることによってのみ、ストアの状態を変更することができます。

レイアウトの準備

いよいよアプリ開発を始めます。bulma.css でレイアウトを整え、Welcomeページ(pages/index.vue)を修正します。

$ npm install @nuxtjs/bulma --save

nuxt.config.js

module.exports = {
 
  /* 中略 */
 
  modules: [
      '@nuxtjs/bulma',
  ],
}

pages/index.vue

<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 に記述していた内容をヘッダとしてコンポーネント化します。

components/AppHeader.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>

layouts/default.vue

<template>
  <div>
    <AppHeader></AppHeader>
    <nuxt/>
  </div>
</template>
 
<script>
    import AppHeader from '@/components/AppHeader.vue';
 
    export default {
        components: {
            AppHeader,
        },
    };
</script>

レイアウト/コンポーネントを作成することでページの細分化ができました。
ページのコンテンツはいったん仮の状態にしておきます。

pages/index.vue

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

犬種リストをDog APIで取得する

API連携部分の開発に進みます。実装の流れは以下のとおりです。

  1. Dog API用のクラスを作成する
  2. ページからAPIのエンドポイントを叩く
  3. 取得した犬種リストをストアに保存する

なお、以降の開発で画面の表示が更新されなかったり、エラー画面が解消されない場合は npm run dev で再度ビルドを行ってみてください。

axios のインストール

非同期通信のために axios を追加で npm install します。

$ npm install axios --save

Dog API用のクラスを作成

api ディレクトリを新規作成してDog API用のクラスを作成します。
犬種リストを取得するメソッドは breeds() として定義しておきます。

api/dog.js

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;

犬種リストを保存するストアを作成

store/index.js を作成し、APIで取得する犬種のリストを保存できるストアを準備します。

store/index.js

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;

...payloadの部分はes6の spread operator(スプレッド演算子)です。

mutations を介して、state.breed_list に犬種のデータを格納できるようにしました。あとはページ側で  fetch() メソッドを定義し、ストアにデータコミットするだけです。

pages/index.vue

<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>

async await の使い方 – Qiita

取得データの確認

Chrome拡張の Vue.js devtools をインストールしておきます。

開発ツールを表示すると、新たに「Vue」タブが追加されているはずです。
「Vue」→「Vuex」に進んで、画像のように breed_list にデータが入ってきていればOKです。

画面に犬種リストを表示する

VuexのmapStateヘルパーを使うことで、ストアからデータを取り出すことができます。

pages/index.vue

<template>
    <section class="container">
        <div class="columns is-multiline">
 
            <!-- v-for で breed_list からループ出力 -->
            <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']),  // mapState ヘルパー
    }
</script>

ここまでの開発で、以下のような犬種リストが表示されていればOKです。

Part.1 の開発はここまでです。Part.2からは画像表示と「いいね!」ボタンの配置、ページング処理の実装を行います。