はじめに

目的

こんにちは、エンジニアの遠藤です。
ミルディアでは、Vue.jsを使ったプロジェクトがいくつかあります。

こうしたプロジェクトに、Vue未経験の方に勉強を兼ねて参画いただくこともあるのですが、
ある程度完成したVueのプロジェクトだと、どこから見たら良いか分からず、また、あまり好き勝手に触れず、思うように進められないケースもあります。

そこで、 vue-cli を使って簡単なテンプレートプロジェクトを作成して、何がどうなっているかを簡単に説明します。

Vue初心者 に CLI はおすすめされていないのですが、実プロジェクトではがっつり使われていたりするため、個人的には最初から vue-cli から触ってしまった方が、手っ取り早いかなとも思います。

もちろん、時間のある方はこちらを上から順番に追ったり、何かの学習コースに参加して、じっくり学習するのも良いですね。

環境

OS: macOS Catalina

想定している読者

  • ちょっとJavaScript書ける
  • ちょっとコマンド打てる

この記事で書くこと

  • vue-cli のインストール手順
  • vue-cli のプロジェクト構築
  • vue機能
    • Component
    • Router
    • Vuex

この記事で書かないこと

  • Vueの基本的な部分(テンプレート構文、算出プロパティ、バインディング、v-if、v-forなどなど)
    • この辺は、 vue + for とか、 vue + if とかでググるとたくさん出てきます。
  • Babel
  • TypeScript
  • Linter
  • Test

目標

簡単なWebページをVueで作って公開できるようになる

事前準備

vue-cli をインストールするには、以下が必要です。
すでにインストール済みの方は無視ししていただいて大丈夫です。

  • homebrew
  • yarn

homebrew のインストール

ターミナルから以下のコマンドを実行します。

$ /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"

URLなどが変わっているかもしれないので、こちらもご確認ください。

yarn のインストール

ターミナルから以下のコマンドを実行します。

$ brew install yarn

URLなどが変わっているかもしれないので、こちらもご確認ください。

vue cli をインストール

ターミナルから以下のコマンドを実行します。

$ yarn global add @vue/cli

プロジェクトの作成

vue のテンプレートプロジェクトを作成

以下のコマンドで、テンプレートプロジェクトの作成が始まります。
プロジェクトは、カレントディレクトリ直下に出力されます。

$ vue create hello-world

オプションを設定していく

上記を実行すると、以下の画面になるかと思います。
カーソルが効くので、 Manually select features を選んでEnterを押します。

? Please pick a preset: (Use arrow keys)
❯ default (babel, eslint) 
  Manually select features 

続いて、有効にする機能を選びます。
Vueの主な挙動を理解するには、RouterとVuexをONにするのが良いと思います。
カーソルで上下移動し、 space で ON/OFF を切り替えて、決まったらEnterを押します。

? Please pick a preset:
❯ Manually select features
 ◯ Babel
 ◯ TypeScript
 ◯ Progressive Web App (PWA) Support
 ◉ Router
 ◉ Vuex
 ◯ CSS Pre-processors
 ◯ Linter / Formatter
 ◯ Unit Testing
 ◯ E2E Testing

history mode は On にしておきましょう。
少し詳しい説明はこちら

? Use history mode for router? (Requires proper server setup for index fallback in production) (Y/n) Y

構成ファイルは、私は package.json に保存しています。

? Where do you prefer placing config for Babel, PostCSS, ESLint, etc.? 
  In dedicated config files 
❯ In package.json

この設定をデフォルトにしますか?
しなくて良いです。

? Save this as a preset for future projects? (y/N) N

あとは待つばかり。

...
⚓  Running completion hooks...

📄  Generating README.md...

🎉  Successfully created project hello-world.
👉  Get started with the following commands:

起動してみる

$ cd hello-world
$ npm run serve

 DONE  Compiled successfully in 1679ms                                                                     16:28:44


  App running at:
  - Local:   http://localhost:8080/ 
  - Network: http://xxx.xxx.xxx.xxx:8080/

  Note that the development build is not optimized.
  To create a production build, run npm run build.

と出ていれば成功です。

以上で、ローカルにサーバが立ち上がり、ファイルをいじれば AutoReload も走ります。

プロジェクトの中身

それでは出来上がったプロジェクトの中をみていきます。

hello-world
├── node_modules/       # npm でインストールされる node モジュール
├── public
│   ├── favicon.ico     # favicon
│   └── index.html      # ベースになるhtml (作成した vue のコードを読み込むだけの html)
├── src
│   ├── assets/         # 画像などはここに置く
│   ├── components/     # component(後述します)を置く
│   ├── router/index.js # url の path に合わせて表示する view を管理してくれる
│   ├── store/index.js  # APIで取ってきたデータなどを一元管理してくれる
│   ├── views/          # view(後述します)ファイルを置く
│   ├── App.vue         # アプリのトップレベルのVueファイル
│   └── main.js         # index.html に App.vue をレンダリングしたり、router, vuex を読み込んだりする
└── package.json        # npm に関する定義ファイルみたいなもの

概ね以上の役割が分かっていればまずは問題ないかと思います。

いじってみる

$ npm run serve

した状態で、 ブラウザの http://localhost:8080 にアクセスすると、以下のような画面が出ていると思います。

hello-world-vue-1

この状態から、 src/components/HelloWorld.vue<template> 内の文字を適当に変えてみましょう。

例えば、以下のようにスッキリさせてみます。

<template>
  <div class="hello">
    <h1>{{ msg }}</h1>
  </div>
</template>

すると、表示は以下のように変わります。

hello-world-vue-2

なるほど。
template の中に書いた html っぽいものが表示されるんだな、というのが分かるかと思います。

解説

components

componentsとは

さて、 HelloWorld.vue というファイルは components ディレクトリに配置されていますが、この
componentとは何でしょうか。
公式を引用すれば

コンポーネントは名前付きの再利用可能な Vue インスタンスです。
ということで、画面を構成するパーツみたいなものになります。
HelloWorld.vue の場合は、ほぼ全ての画面要素を表示してしまっているために分かりにくいですが、例えば特殊な動きをするボタンだったり、テキストフィールドだったり、というものを便利に使い回すことができます。
html でも、 <button>などと書けば、ブラウザが勝手にボタンを表示してくれますが、そういうパーツを自分で定義できます。

でも、そんなパーツは html タグに class でも付けて作れば良くない?という意見もあるかと思います。
それも一理あるのですが、components にもメリットはあります。
もう一度、 HelloWorld.vue を見てみます。

<style scoped>
h3 {
  margin: 40px 0 0;
}
ul {
  list-style-type: none;
  padding: 0;
}
li {
  display: inline-block;
  margin: 0 10px;
}
a {
  color: #42b983;
}
</style>

下の方に、 style タグがあることが分かります。
そして、 scoped と書かれています。

これを記載しておくことで、最終的にビルドした際に自動的に id が付与され、別の場所で使われなくなります。

css の命名規則だと BEM が有名ですが、そこまでしっかりした規則を設けなくても、 css が競合しなくなります。
また、処理を含めたパーツ内の情報が全て一つのファイルの中にまとまるので、見通しが良くなります。

一方で、 script と css が一つのファイルに入ってしまっていることで、デザイナとエンジニアが同時作業し辛いといったデメリットもあるかなと思います。

components の描画

さて、簡単に components の役割を見てきましたが、では、そもそも src/components/HelloWorld.vue は誰が表示させているのか?ということで、 src/views/Home.vue を見てみます。

<template>
  <div class="home">
    <img alt="Vue logo" src="../assets/logo.png">
    <HelloWorld msg="Welcome to Your Vue.js App"/> <!-- (3) 定義済みのコンポーネントは普通の html タグと同じように使える -->
  </div>
</template>

<script>
// @ is an alias to /src
import HelloWorld from '@/components/HelloWorld.vue' // (1) 使いたいcomponent を importする

export default {
  name: 'Home',
  components: {
    HelloWorld // (2) components の中に定義する
  }
}
</script>

HelloWorld.vue は以下の流れで、 Home.vue 内に描画されることになります。

  • (1) 使いたいcomponent を importする
  • (2) components の中に定義する
  • (3) 定義済みのコンポーネントは普通の html タグと同じように使える

同様の手順を使えば、 About.vue の中で描画したり、新規作成した component 内などでも使うことが可能です。

views と components

さて、Home.vue をパッと見ると、 <template><script> から構成されており、 HelloWorld.vue の中身と大きく変わらないことが分かるかと思います。

というわけで、 views の中に配置されてはいるけど、これも components の一つだと考えてしまって差し支えないと思います。
では、なぜ別のフォルダにいるのか?ということですが、これは router を見ていただくと何となく理解できると思います。

router

router とは

常にトップページからの遷移しか想定しないWebページであれば問題ないのですが、例えばURLを直接ブラウザに入力して画面遷移したいユーザがいた場合(特定のページをブックマークしたりするケースなど)に、とても不便な作りとなってしまいます。
router では、URLとコンポーネントをマッピングしてくれるので、普通のページ遷移と同じような体験をユーザに提供できたりします。

さて HelloWorld.vue を表示していたのは、 Home.vueでしたが、ではHome.vueは誰が表示しているでしょうか。
src/router/index.js を見てみます。

import Vue from 'vue'
import VueRouter from 'vue-router'
import Home from '../views/Home.vue' // Home.vue コンポーネントをインポートする

Vue.use(VueRouter)

// どのパスへアクセスされたら、どこへ飛ばすかを記載する
const routes = [
  {
    path: '/',
    name: 'Home',
    component: Home
  },
  {
    path: '/about',
    name: 'About',
    // route level code-splitting
    // this generates a separate chunk (about.[hash].js) for this route
    // which is lazy-loaded when the route is visited.
    component: function () {
      return import(/* webpackChunkName: "about" */ '../views/About.vue') // この記載方法だと各ルートを読み込んだタイミングで遅延ロードされるらしい
    }
  }
]

const router = new VueRouter({
  mode: 'history',
  base: process.env.BASE_URL,
  routes
})

export default router

この中で、どのURLにアクセスしたら、どのコンポーネントを描画するか制御しています。

こちらを見ると、 views/ ディレクトリから読み込みしていることが分かります。
つまり、 vue-cli がデフォルトで作成してくれるテンプレートプロジェクトでは

  • src/views → 各パスに対応した vue を配置する
  • src/components → views に組み込まれる画面のパーツを配置する

というルールで作成されていることになります。
これが、 views と components のざっくりとした違いです。

これは絶対的なルールではありませんが、それぞれ何がどう参照されて構成されるかを理解することで
どのようなルールのプロジェクトでも、全体像を掴みやすくなるのかなと思います。

この router ファイルは main.js から参照されることになります。

Vuex

ここまで component, router と見てきて、静的なページであればもう作ることができると思います。
では、component 間でデータを共有したい場合はどうしたら良いでしょうか?

例えば、APIで取得してきたあるデータを、複数のコンポーネントから参照したい、というケースは多々あるかと思います。
そんな時に使えるのが Vuex です。
React などで Redux を経験されているとイメージが湧きやすいですが、初見の方に完結に説明するのが難しいです。

Vuex 公式のざっくり解説

ですので、まずは公式を一通り見ていただくのが一番分かりやすいです。
この記事では、公式の解説が、ここまで作ってきたプロジェクトのどこに該当するのか、どう当てはめられるのかを簡単に説明していく形を取りたいと思います。

インストール

インストール
最初に vue-cli でオプションを選択した際に自動的にインストールされています。
その他の手順で構築した場合は、公式の方法を確認しましょう。

Vuex 入門

Vuex 入門
これとほぼ同じコードが src/store/index.js にあります。
公式では、宣言した vuexインスタンスをそのまま使うサンプルになっていますが、 hello-world プロジェクトでは、アプリケーション全体で使えるように、すでに設定されています。
その設定方法は次のステートで説明されます。

ステート

ステート
この中で

Vuex は、ルートコンポーネントに store オプションを指定することで (これは、 Vue.use(Vuex) で有効にできます)、すべての子コンポーネントにストアを "注入" する機構を提供しています

と説明されています。
これと同じことが、 src/main.js に記述されています。

import Vue from 'vue'
import App from './App.vue'
import router from './router'
import store from './store'

Vue.config.productionTip = false

new Vue({
  router,
  store, // 注入されている
  render: function (h) { return h(App) }
}).$mount('#app')

これがあることで、 hello-world プロジェクトでは、 Vuex が最初から使えるようになっています。

では、公式のステートまでを読まれた前提で、 hello-world プロジェクトに手を入れていきます。

まずは、 src/store/index.js を以下のように編集します。

export default new Vuex.Store({
  state: {
    count: 1, // ← 追加
  },
  mutations: {
  },
  actions: {
  },
  modules: {
  }
})

次に、 HelloWorld.vue を以下のように修正します。

<template>
  <div class="hello">
    <h1>{{ count }}</h1> <!-- (3) 参照している値はそのまま template 内で使用可能 -->
  </div>
</template>

<script>
  import { mapState } from 'vuex' // (1) mapState をインポートする
export default {
  name: 'HelloWorld',
  props: {
    msg: String
  },
  computed: {
    ...mapState(
      ["count"] // (2) 算出プロパティに定義した count をセットする
    )
  }
}
</script>

// 以下省略

すると、ブラウザには、 count にセットした値が表示されているかと思います。

ゲッター

ゲッター
ゲッターはデフォルトではテンプレートに記載されていません。
必要に応じて定義できれば良いと思います。
使い方は state の参照と大きく違いありませんので、割愛します。

ミューテーション

ミューテーション
ミューテーションには実際に、 state の値を変更する方法が記載されています。

hello-world プロジェクトで使ってみましょう。

まず src/store/index.js を以下の通り変更します。

import Vue from 'vue'
import Vuex from 'vuex'

Vue.use(Vuex)

export default new Vuex.Store({
  state: {
    count: 0,
  },
  // 以下を公式と同じように修正している
  mutations: {
    increment (state, payload) {
      state.count += payload.amount
    }
  },
  actions: {
  },
  modules: {
  }
})

続いて、 HelloWorld.vue を以下のように修正します。

<template>
  <div class="hello">
    <h1>{{ count }}</h1>
    <button v-on:click="clickButton">count up</button> <!-- (4) ボタンを追加 -->
  </div>
</template>

<script>
  import { mapState, mapMutations } from 'vuex' // (1) import を追加
export default {
  name: 'HelloWorld',
  props: {
    msg: String
  },
  computed: {
    ...mapState(
      ["count"]
    )
  },
  methods: {
    ...mapMutations({
      add: 'increment' // (2) `this.add()` を `this.$store.commit('increment')` にマッピングする
    }),
    clickButton: function () { // (3) ボタンを押された時のfunction
      this.add({
        amount: 10
      });
    }
  }
}
</script>
// 以下省略

まずは、 import 文で mapMutations を import します。
その後、 methods 内に、ボタンを押された際の function と、 store に記載した mutation を定義します。
あとは、<template> 内に、ボタンを設置すれば完成です。
ボタンを押すたびに、画面に表示された数値が10ずつカウントアップされるのが分かります。

アクション

アクション
mutation で値を変更できることはわかりましたが、例えばAPI呼び出しなどで値を変えたい場合はどうすれば良いか。
という話が、 action に書かれています。

component からの使い方は、 mutation と大きく変わらないので割愛いたします。
ただ、実際のプロジェクトでは、非同期での呼び出しは頻繁に出てくるので、mutation とあわせて、公式の解説はしっかり読んでおくと良いと思います。

モジュール

モジール
stateに全部の定義を書いていくと膨大になってしまうので、細かく分割して管理もできるよ、ということが書かれています。
その場合、 mapState などの書き方が変わるので注意しましょう。

ビルドする

さて、長々と解説をしてきましたが、あとは、これをビルドすればWebページが完成します。

まずは

$ npm run serve

を ctrl + c などで 一旦終了します。
そして、そのまま、

$ npm run build

としてください。

そうすると、新しく dist ディレクトリが出来上がります。
この中には、 html, js, css の一式が入っていますので、dist配下のものだけを Amazon S3 などに置いて静的ページとして公開したり、 Github pages などで公開することができます。
また、以下の記事などを参考にしていただき、localでの動作確認などもできます。

ちょっとしたアプリケーション機能を持たせるために、 heroku や Azure App Service などの PaaS を使って公開することも可能です。
これはまた長くなりそうなので、別記事にしたいと思います。

おわりに

以上で Vue の解説は終わりです。

とりあえず何か適当に作って動かしてみる、というのがお手軽にできるので、色々と触ってみるととても楽しいと思います。

Vue は公式の解説を見ても分かる通り、色々な書き方があり、そのサンプルをこのプロジェクトではどうやって使うのよ?と迷子になることが良くあります。
ここまでの記事の内容が何となくでも理解できていれば、いきなり Vue のプロジェクトに飛び込んでも、迷子になることは減るのではないでしょうか。

この記事が誰かのお役に立てれば幸いです。
それでは。