Vue.jsで画像を一覧表示する(vue入門)


# はじめに
必要があってWebアプリ開発の領域を少し調べてみたのだが、またしばらくWebアプリを作ることはなさそうなので、備忘録的に開発方法をまとめてみた。
ここではデモ用のアプリとして、[unsplash](https://unsplash.com/)の写真を一覧表示するWebアプリを作成する。また、フロントエンドCSSライブラリ(UIのデザイン)としてBootstrapを用いる。

WebアプリやWebのフロントエンド開発者はHTML5+CSS+Javascriptで開発するのが一般的である。最近のフロントエンド開発においては次のような3つのメジャーフレームワークとモバイルアプリ開発環境があり、それぞれWebアプリと同様の開発方法でスマホアプリを作ることが可能となっている。

| フレームワーク | モバイル開発環境 |
|:--|:-:|
| [React](https://reactjs.org) (by Facebook) | [Capacitor](https://capacitorjs.com/solution/react) or [React Native](https://facebook.github.io/react-native/) |
| [Angular](https://angular.io) (by Google) | [Capacitor](https://capacitorjs.com/solution/angular), [NativeScript](https://www.nativescript.org) or [Ionic](https://ionicframework.com) + [Cordova](https://cordova.apache.org) |
| [Vue.js](https://jp.vuejs.org/index.html) | [Capacitor](https://capacitorjs.com/solution/vue), [Weex](https://weex.apache.org) or [Cordova](https://cordova.apache.org) |

ここではVue.jsをつかったWebアプリ開発についてまとめる。

最終的に出来上がったサイトのサンプルは[GitHub Pages](https://mktshhr.github.io/vueTest/)にUpしてある。

# 1. 開発環境
* macOS High Sierra ver.10.13.6
* node.js: ver.10.15.1
* npm: ver.6.8.0
* vue.js (vue cli): ver.3.4.0
* bootstrap-vue: @2.0.0-rc.14
* Visual Studio Code: ver.1.32.2
* Google Chrome: ver.72.0.3626.121

# 2. 環境構築

## 2-1. [node.js](https://nodejs.org/ja/) (npm) のインストール
* [Homebrew](https://brew.sh/index_ja)をセットアップ
 * `/usr/bin/ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)"`
* brewでnodeをインストール
 * `brew install nodebrew`
 * `nodebrew install-binary latest`
 * `nodebrew use v10.15.1`
* npmはnode.jsをインストールすると同時にセットアップされる。

## 2-2. [yarn](https://yarnpkg.com/ja/)のインストール
* npmからyarnに徐々にトレンドが移っている様子
* `brew install yarn`

## 2-3. [vue](https://cli.vuejs.org)のインストール
*  `yarn global add @vue/cli` (もしくは `npm install -g @vue/cli`)

# 3. プロジェクト("my-project")作成
## 3-1. vue create
* `vue create my-project` (もしくは `vue ui`でGUI付き)でセットアップ
 * Preset選択で、`Manually select features` を選ぶ。(RouterやVuexを使わない場合は `default (babel, eslint) ` を選んでも良い。）
 * 必要に応じて、Router, Vuexを追加する。ここでは追加しない前提で話を進めるが、あとで追加するのは面倒なのでRouter, Vuexを使う予定がある場合は最初に設定しておいた方が良い。

* `cd my-project`
 * 特に断りがないときはこのルートフォルダでnpmやyarnなどのコマンドを叩く。 


## 3-2. フォルダ構成
初期化直後の状態。
Webアプリ開発では主にsrcフォルダとpublicフォルダの中身を変更する。

```
.
├── .gitignore
├── README.md
├── babel.config.js
├── node_modules
├── package.json
├── public
│   ├── favicon.ico
│   └── index.html
├── src
│   ├─- App.vue
│   ├── assets
│   │   └── logo.png
│   ├── components
│   │   └── HelloWorld.vue
│   └── main.js
└── yarn.lock
```

# 4. デバッグ方法
## 4-1. ビルド
* `yarn serve` でWebサーバが起動してブラウザで表示できるようになる。
* 表示されているURL(例えば、http://localhost:8080/ )にChromeでアクセスする

## 4-2. デバッグ
ChromeとVS Codeを使ったデバッグについては[公式ページ(VS Code によるデバッグ)](https://jp.vuejs.org/v2/cookbook/debugging-in-vscode.html)をみると良い。ブレークポイントを張りながらデバッグできるので便利。

### 4-2-1. Chromeを用いたデバッグ
 * Chromeのデベロッパツールを開く（Windows: F12キー、macOS: option+command+"i"）
 * [Vue.js devtools](https://chrome.google.com/webstore/detail/vuejs-devtools/nhdogjmejiglipccpnnnanhbledajbpd?hl=ja)というChrome機能拡張をインストールすると、デベロッパツールの上にあるタブに"vue"という項目が追加され、vue毎のプロパティをライブ編集できるようになる。
 * [Vue.js devtools](https://chrome.google.com/webstore/detail/vuejs-devtools/nhdogjmejiglipccpnnnanhbledajbpd?hl=ja)を使うときは、以下の設定ファイル(vue.config.js)をルートフォルダに追加する。
 * デバッグ時は`yarn serve`してから、URLを入れてChromeで表示する。

```javascript:vue.config.js
module.exports = {
  configureWebpack: {
    devtool: 'source-map'
  }
}
```
### 4-2-2. VSCodeでのデバッグ
* プロジェクトのルートフォルダに、".vscode"フォルダを作成し、その中に"launch.json"ファイルを作成する。
* launch.jsonの中身は以下のように記載する。

```json:.vscode/launch.json
{
  "version": "0.2.0",
  "configurations": [
    {
      "type": "chrome",
      "request": "launch",
      "name": "vuejs: chrome",
      "url": "http://localhost:8080",
      "webRoot": "${workspaceFolder}/src",
      "breakOnLoad": true,
      "sourceMapPathOverrides": {
        "webpack:///src/*": "${webRoot}/*"
      }
    }
  ]
}
```
* `yarn serve`後、VSCodeの左のデバッグアイコン"Run(Shift+Command+D)"を選ぶと、"vuejs: chrome"というプルダウンリストが表示されるので、その左のRunボタンを押すか、キーボードの"F5"ボタンでデバッグを開始できるようになる。
* ブレークポイントはVSCode内で設定できる。


### 4-2-3. vueの設定
vue cli ver.3からvueの設定ファイルは自動生成されなくなった。設定する場合は自分でルートフォルダに`vue.config.js`ファイルを作成し、[定義](https://cli.vuejs.org/config/#vue-config-js)を参考に設定を記載する。webpack関連は`vue.config.js`ファイル内にconfigureWebpackオプションを記載して設定できるが、outputDirなどvueの設定として定義されているものがある場合は、[configureWebpackオプションを使わず、vueの設定を利用しなければならない。](https://cli.vuejs.org/guide/webpack.html#simple-configuration)

# 5. 開発
ここでは[unsplash](https://unsplash.com/)の写真を一覧表示するページを作る。APIとしては[picsum](https://picsum.photos/)のものを用いる。
ここで利用したソースコードは[github](https://github.com/mktshhr/vueTest/) にある。

## 5-1. bootstrap-vueとaxiosのセットアップ
UIデザインにはbootstrap-vueを利用する。また、HTTPの通信には[axios](https://github.com/axios/axios)を利用する。(昔はvue-resourceが使われていたらしい。)
以下のようにしてプロジェクトのnode_modulesにインストールする
 
```
npm install --save bootstrap
npm install --save bootstrap-vue
npm install --save axios
```

## 5-2. コード変更
* 変更はmain.jsとHelloWorld.vueの2ファイルのみ。
* (1) main.js
 * [Bootstrap Vue](https://bootstrap-vue.js.org/docs)を使うため、src/main.jsを以下のように書き換えた。

```javascript:main.js
import Vue from 'vue'
import App from './App.vue'
import BootstrapVue from "bootstrap-vue"

Vue.use( BootstrapVue )

import 'bootstrap/dist/css/bootstrap.css'
import "bootstrap-vue/dist/bootstrap-vue.css"

Vue.config.productionTip = false

new Vue( {
  render: h => h( App ),
} ).$mount( '#app' )
```
 
* (2) HelloWorld.vue
 * src/components/HelloWorld.vueを以下のように書き換えた。

```vue:HelloWorld.vue
<template>
  <div class="hello">
    <!-- (a) -->
    <h1>{{ msg }}</h1>
    <div class="row">
      <!-- (b) -->
      <div
        class="col-sm-6 col-md-3 col-xl-2"
        v-for="(result, idx) in processedResults"
        :key="result.id"
      >
        <!-- (c) -->
        <a class="card" :href="result.post_url">
          <!-- (d) -->
          <h5 class="card-title">{{idx}}:{{result.author}}</h5>
          <!-- (e) -->
          <img class="card-img" :src="result.url" :alt="result.post_url">
          <p class="card-text">{{result.filename}}.</p>
        </a>
      </div>
    </div>
  </div>
</template>

<script>
const axios = require("axios");
export default {
  name: "HelloWorld",
  props: {
    msg: String
  },
  data() {
    return {
      info: null,
      results: []
    };
  },
  // (f) 
  mounted: function() {
    {
      axios
        .get("https://api.coindesk.com/v1/bpi/currentprice.json")
        .then(response => (this.info = response.data.bpi));

      axios
        .get("https://picsum.photos/list")
        .then(response => (this.results = response.data));
    }
  },
  computed: {
    // (g) 
    processedResults: function() {
      let results = this.results;
      for (let i = 0; i < results.length; i++) {
        results[i].url = "https://picsum.photos/300/200?image=" + results[i].id;
      }
      return results;
    }
  }
};
</script>

<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped>
a {
  color: #42b983;
}
</style>
```

* (a) `{{ msg }}`部分はpropsのパラメータとなっており、親側(/src/App.vue)から設定される。App.vueをみると、`<HelloWorld msg="Welcome to Your Vue.js App"/>`のようにしてmsgに文字列が設定されている。
* (b-1) `class="col-sm-6 col-md-3 col-xl-2"`部分は[bootstrap-vueのLayout and Grid System](https://bootstrap-vue.js.org/docs/components/layout/)を利用し、画面の横幅を１２等分したうち、どのような割合でグリッドに分割するかを示している。`col-sm-6`は画面の幅がSmall(≥576px)の時、6/12=1/2のところでグリッド分割することを示している。つまり、一行には横並びに２つの画像が表示されるようになる。ここでは、"col-sm-6 col-md-3 col-xl-2"のように３つの指定がある。これは画面幅がSmall(sm≥576px)の時に２つ(6/12ずつ)、Medium(md≥576px)の時に４つ(3/12ずつ)、Extra large(xl≥576px)の時に6つ(2/12ずつ)、画像が表示されることを示している。Smallより小さいとき(<576px)は１つ(12/12)の画像が表示される。[bootstrap-vueのグリッドオプション](https://bootstrap-vue.js.org/docs/components/layout/#grid-options)によれば、
 * Extra small (xs) <576px 
 * Small (sm) ≥576px 
 * Medium (md) ≥768px
 * Large (lg) ≥992px
 * Extra large (xl) ≥1200px
のように定義されている。

* (b-2) `v-for="(result, idx) in processedResults"` 部分は、v-forによって`processedResults`という配列（実際には算出プロパティ(g)）を一つ一つ処理することを示している。個々の要素は第一引数であるresultで受け取れる。第二引数(idx)は配列のインデックス。`:key="result.id"`とすることで、第一引数resultにあるidをv-forで必要となるkeyとして設定している。`key`ではなく`:key`とすることで"result.id"が文字列ではなくパラメータとして扱われる。ここでresultは以下のようなデータを持つ。このことは、実際に通信を行うことで確認できる。上述の[Vue.js devtools](https://chrome.google.com/webstore/detail/vuejs-devtools/nhdogjmejiglipccpnnnanhbledajbpd?hl=ja)を使ってChromeの開発者モードを使っても見ることができる。

```json
{
"format":"jpeg",
"width":5616,
"height":3744,
"filename":"0000_xxxx.jpeg",
"id":0,
"author":"xxxxxxxx",
"author_url":"https://unsplash.com/@xxxxx",
"post_url":"https://unsplash.com/photos/xxxxx",
"url":"https://picsum.photos/300/200?image=0"
}
```
* (c) `<a class="card" :href="result.post_url">`部分では[bootstrap-vueのcard](https://bootstrap-vue.js.org/docs/components/card/)を利用した。カードをクリックした際のURLを`:href`として設定した。`href`でなく`:href`なのは上述の通り、"result.post_url"をパラメータとして利用するため。
* (d) `<h5 class="card-title">{{idx}}:{{result.author}}</h5>` 部分では、カードのタイトルとして"0:xxxxxxxx"のような物を設定している。{{と}}で囲むことでパラメータを展開して利用する。
* (e) `<img class="card-img" :src="result.url" :alt="result.post_url">` 部分では"result.url"のURLにある画像を表示する。`
* (f) `mounted: function()`は[vueのライフサイクル](https://jp.vuejs.org/v2/guide/instance.html#ライフサイクルダイアグラム)でページが表示される直前に呼び出される関数である。ここでは、mountedの中で、`https://picsum.photos/list`にアクセスし、`results`パラメータに応答を記録している。.thenで繋げているのは非同期で結果が返ることを示唆している。
* (g) 前述の(b)で配列として説明した`processedResults`はcomputed:で定義された算出プロパティとなっている。内部で`this.results`を利用しているため、(f)の結果が返り、`this.results`が更新されると、processedResultsが呼び出される。そして、(b)のv-for部分が更新され、画像のリストが表示される。

## 5-3. 動作結果
ブラウザで見ると以下のように表示される。左側にVueの表示、右側がChromeのディベロッパツールを表示させている。
<img width="1651" alt="vueTest.png" src="https://qiita-image-store.s3.amazonaws.com/0/123458/99a86f63-16d0-fb5e-aa76-6d552ada996d.png">

## 5-4. gitでcloneした場合のセットアップ
* `npm install`もしくは`yarn install`でnode_modulesをセットアップする。

# 6. デプロイ方法
* `yarn build` すると、デプロイ用のdistフォルダが生成される。

# 7. まとめ
vue cli3 を使った例として、セットアップからデバッグまでを説明し、画像をリスト表示させるWebアプリを作成した。