LuftGarden - 熊本のWebサイト・ホームページ制作スタジオ

BLOG技術ブログ

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

Nuxt.js とは

Nuxt.js は「ユニバーサルな Vue.js アプリケーションを構築するためのフレームワーク」です。
ユニバーサルという言葉を辞書で引くと、「すべてに共通であること」、「普遍的・一般的」という意味になります。

ここで言う「ユニバーサル」とは、「サーバサイドとクライアントサイドの違いを抽象化することで無視して、一貫した方法でUIレンダリングすること」です。 公式ドキュメント を見れば最も理解が進むと思います。

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

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

Nuxt.jsの主な機能は以下のようになっています(公式ドキュメントより引用)。

  • Vue ファイルで記述できること
  • コードを自動的に分割すること
  • サーバーサイドレンダリング
  • 非同期データをハンドリングするパワフルなルーティング
  • 静的ファイルの配信
  • ES6/ES7 のトランスパイレーション
  • JS と CSS のバンドル及びミニファイ
  • Head 要素の管理
  • 開発モードにおけるホットリローディング
  • SASS, LESS, Stylus などのプリプロセッサのサポート
  • HTTP/2 push headers ready
  • モジュール構造で拡張できること

nuxt generateを使えばNuxt.jsのプロジェクトは静的ファイルとしてもデプロイ可能です。
一般的な企業Webサイトの制作なんかにもNuxt.jsは有効に活用できると思います。

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

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

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

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

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

機能要件

簡単にまとめると以下の4点です。

  • サーバーから画像のリストを取得して表示
  • 画像をクリックするとモーダルウィンドウで拡大表示
  • 最新画像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です。

inukoroプロジェクトのWelcomeページ。

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

重要なディレクトリやファイルは以下のとおりです(画像はPhpStormのキャプチャ)。
 公式のビューに関する図解 も併せてご覧ください。

ディレクトリ構成の画面キャプチャ

pagesディレクトリ

本格的なWebアプリの開発に入る前に、ディレクトリについて説明をしておきます。

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 で再度ビルドを行えばOKです。

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 に関しては以下の記事が参考になります。
 async await の使い方 – Qiita

取得データの確認

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

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

Vue.js devtoolsの画面キャプチャ

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

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 の開発はここまでです。お疲れさまでした。
Nuxt.js の開発中はホットリローディングが有効になるため非常に捗りますね。

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

PAGETOP