Vue.jsについてまとめて、カウンターアプリを作ってみる。

# この記事は？

今まで、フロントエンドJavaScriptフレームワークはReact.jsを少し触ったことがあったのですが、今後Vue.jsを仕事で使いそうなのでまとめます。この記事では以下のようなことをまとめ、Vue.jsにHello Worldとしてカウンターアプリを作ってみます。

* Vue.jsの概要
* Vue.jsのエコシステム

![logo.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/202545/091ba3d8-fd98-faee-ba2f-63753f724edc.png)

# Vue.jsとは
Webフロントエンドを作成するためのJavaScriptフレームワークの1つです。Vue.jsと同様のフレームワークにはReact.js、Angular.jsなどがあります。公式ドキュメントでVue.jsはプログレッシブフレームワークとして紹介され、すでに存在するフロントエンドに対してプラガブルに差し込んだりすることが可能です。
Vue.jsではそれぞれのコンポーネントでHTML、CSS、JavaScriptを内包しています。また、状態変化はVue.jsによって管理され、反応的に画面の再構成を行います。
Vue.jsの特徴には以下の2つのものがあります。

* コンポーネントシステム
* リアクティブ

### コンポーネントシステム
Vue.jsにおけるコンポーネントとはあらかじめ定義されたオプションを持つインスタンスです。Vue.jsでは小さなコンポーネントを組み合わせて大規模なアプリケーションを作成することが想定されています。
コンポーネント志向の設計では、画面を構成する。JavaScript、HTML、CSSを1つのコンポーネントとして扱い他の機能と分離できるという特性があります。それにより、「JavaScriptやCSSがどこで使われているか」といった複雑性を排除することができます。
また、Vue.jsのコンポーネントはツリー構造のように考えるとができます。


![20190905165358.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/202545/1d98a718-b8e8-9239-18fe-15c9b958a670.png)


Vueコンポーネントを登録しておくことにより、他のコンポーネントからの利用することが可能となります。

```vuejs

// todo-itemというコンポーネントを登録
Vue.component('todo-item', {
  template: '<li>This is a todo</li>'
})
```

登録したコンポーネントは下記のように利用することができます。

```vuejs

// 登録したコンポーネントを利用
<ol>
  <todo-item></todo-item>
</ol>
```

また、利用側コンポーネントから、利用される側のコンポーネントへデータを受け渡すこともできます。

### リアクティブ
Vue.jsではDataをプレーンなJavaScriptオブジェクトとして持ち、それらが更新されるとリアクティブに画面が更新されます。
各コンポーネントはそれぞれが`Watcherインスタンス`と呼ばれるものを持っています。
Watcherはコンポーネントは描写時にふれたデータをすべて依存性として管理しており、依存性の変更が検知されるとWatcherが再描写の指示を行う仕組みになっています。

![render.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/202545/24cea009-5c9f-613e-e158-af0012102800.png)


# Vue.jsのエコシステム
Vue.jsのエコシステムには様々なものがあります。

### 開発ツール
主なVue.jsの開発ツールとして以下のようなものがあります。

* [vue-devtools](https://github.com/vuejs/vue-devtools)
  * Chrome、Firefox、Electron等の拡張機能として提供される、Vue.jsの開発者ツールです。
  * コンソールを開かなくても、データの中身が見れたりします。
* [Vue CLI](https://cli.vuejs.org/)
  * Vue.jsの開発を手助けするためのCLIです。
  * ブランクプロジェクトの作成等が行えます。
* [Vue Loader](https://vue-loader.vuejs.org/)
  * webpackをロードするためのツール

### コアライブラリー
Vue.jsのコアライブラリとして以下のようなものがあります。

* [Vuex](https://vuex.vuejs.org/ja/)
  * Vue.jsのステートを管理するためのライブラリです。
* [Router](https://router.vuejs.org/ja/)
  * Vue.jsの公式のルーターです。
  
# Hello World
Hello Worldとしてカウンターアプリを作成してみたいと思います。

## 環境

* Vue CLI(v3.11.0)
* Node(v11.9.0)
* Chrome

## カウンターアプリの作成

まずはブランクプロジェクトを作成します。ブランクプロジェクトはVue CLIを用いて作成します。

```
$ vue create hello-world-countor
```

すると以下のようなことをまず聞かれます。

```

Vue CLI v3.11.0
? Please pick a preset: (Use arrow keys)
> default (babel, eslint)
  Manually select features
```

どうやら、babel等のツールを入れるかどうか選べるみたいですね。
今回はとりあえずHello Worldがしたいだけなので、深く突っ込まずに`default`を選択します。
以下のようなブランクプロジェクトが作成されました。

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

`package.json`はこんな感じになってます。

```json

{
  "name": "hello-world-counter",
  "version": "0.1.0",
  "private": true,
  "scripts": {
    "serve": "vue-cli-service serve",
    "build": "vue-cli-service build",
    "lint": "vue-cli-service lint"
  },
  "dependencies": {
    "core-js": "^2.6.5",
    "vue": "^2.6.10"
  },
  "devDependencies": {
    "@vue/cli-plugin-babel": "^3.11.0",
    "@vue/cli-plugin-eslint": "^3.11.0",
    "@vue/cli-service": "^3.11.0",
    "babel-eslint": "^10.0.1",
    "eslint": "^5.16.0",
    "eslint-plugin-vue": "^5.0.0",
    "vue-template-compiler": "^2.6.10"
  },
  "eslintConfig": {
    "root": true,
    "env": {
      "node": true
    },
    "extends": [
      "plugin:vue/essential",
      "eslint:recommended"
    ],
    "rules": {},
    "parserOptions": {
      "parser": "babel-eslint"
    }
  },
  "postcss": {
    "plugins": {
      "autoprefixer": {}
    }
  },
  "browserslist": [
    "> 1%",
    "last 2 versions"
  ]
}
```

サーバの起動コマンドの定義がされていたり、`core-js`、`vue`が依存に入ってたりしますね。
とりあえずDevサーバを起動して動かしてみます。`vue-cli-service serve`で起動できそうです。

すると以下のような画面が表示されました。

![first-vue-app.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/202545/3ef7d2f4-3cd3-c9c4-660a-706234a3bfd6.jpeg)


この画面は`App.vue`と`HelloWorld.vue`で構成されています。
ソースコードは下みたいな感じ

`HelloWorld.vue`

```vuejs

<template>
  <div class="hello">
    <h1>{{ msg }}</h1>
    <p>
      For a guide and recipes on how to configure / customize this project,<br>
      check out the
      <a href="https://cli.vuejs.org" target="_blank" rel="noopener">vue-cli documentation</a>.
    </p>
    <h3>Installed CLI Plugins</h3>
    <ul>
      <li><a href="https://github.com/vuejs/vue-cli/tree/dev/packages/%40vue/cli-plugin-babel" target="_blank" rel="noopener">babel</a></li>
      <li><a href="https://github.com/vuejs/vue-cli/tree/dev/packages/%40vue/cli-plugin-eslint" target="_blank" rel="noopener">eslint</a></li>
    </ul>
    <h3>Essential Links</h3>
    <ul>
      <li><a href="https://vuejs.org" target="_blank" rel="noopener">Core Docs</a></li>
      <li><a href="https://forum.vuejs.org" target="_blank" rel="noopener">Forum</a></li>
      <li><a href="https://chat.vuejs.org" target="_blank" rel="noopener">Community Chat</a></li>
      <li><a href="https://twitter.com/vuejs" target="_blank" rel="noopener">Twitter</a></li>
      <li><a href="https://news.vuejs.org" target="_blank" rel="noopener">News</a></li>
    </ul>
    <h3>Ecosystem</h3>
    <ul>
      <li><a href="https://router.vuejs.org" target="_blank" rel="noopener">vue-router</a></li>
      <li><a href="https://vuex.vuejs.org" target="_blank" rel="noopener">vuex</a></li>
      <li><a href="https://github.com/vuejs/vue-devtools#vue-devtools" target="_blank" rel="noopener">vue-devtools</a></li>
      <li><a href="https://vue-loader.vuejs.org" target="_blank" rel="noopener">vue-loader</a></li>
      <li><a href="https://github.com/vuejs/awesome-vue" target="_blank" rel="noopener">awesome-vue</a></li>
    </ul>
  </div>
</template>

<script>
export default {
  name: 'HelloWorld',
  props: {
    msg: String
  }
}
</script>

<!-- Add "scoped" attribute to limit CSS to this component only -->
<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>
```


`App.vue`

```vue

<template>
  <div id="app">
    <img alt="Vue logo" src="./assets/logo.png">
    <HelloWorld msg="Welcome to Your Vue.js App"/>
  </div>
</template>

<script>
import HelloWorld from './components/HelloWorld.vue'

export default {
  name: 'app',
  components: {
    HelloWorld
  }
}
</script>

<style>
#app {
  font-family: 'Avenir', Helvetica, Arial, sans-serif;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-align: center;
  color: #2c3e50;
  margin-top: 60px;
}
</style>
```

`*.vue`で定義されるVueコンポーネントは`<script>`、`<style>`、`<template>`で構成されるみたいです。
名前の通りそれぞれでテンプレート、スクリプト、スタイルを書きます。

`App.vue`をもう少し細かく見てみます。まずは`template`から

```vue

<template>
  <div id="app">
    <img alt="Vue logo" src="./assets/logo.png">
    <HelloWorld msg="Welcome to Your Vue.js App"/>
  </div>
</template>
```
ロゴの表示と`HelloWorld`コンポーネントを表示してます。
`msg`の所でコンポーネントに対して変数を渡せるみたいですね。
変数の参照は`HelloWorld.vue`の`{{ msg }}`の所です。

次に`script`を見てみます。

```vuejs

<script>
import HelloWorld from './components/HelloWorld.vue'

export default {
  name: 'app',
  components: {
    HelloWorld
  }
}
</script>
```

`HelloWorld.vue`のインポートとコンポーネントとしての登録が行われています。
ここでは、`コンポーネントシステム`の所で説明したコンポーネント登録のやり方とは違う[ローカルのコンポーネント登録](https://jp.vuejs.org/v2/guide/components-registration.html#%E3%83%AD%E3%83%BC%E3%82%AB%E3%83%AB%E7%99%BB%E9%8C%B2)が行われています。

最後に`style`

```vuejs

<style>
#app {
  font-family: 'Avenir', Helvetica, Arial, sans-serif;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-align: center;
  color: #2c3e50;
  margin-top: 60px;
}
</style>
```
ここではCSSを記述してますね。Vue.jsの勉強として突っ込めそうなところはないのでこれ以上の深堀はしないです。

この`App.vue`を`main.js`でVueオブジェクトで読み込んでVueインスタンスを作成しています。

```javascript


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

Vue.config.productionTip = false

new Vue({
  render: h => h(App),
}).$mount('#app')
```

ちなみにここの`h`は`hyperscript`の略で、`hyperscript`は仮想DOMの実装のことを表すそうです。

ざっくりとプロジェクトを把握したところで、カウンターアプリを作っていきます。
せっかくなのでVue.jsのロゴと`Welcome to Your Vue.js App`の所は残しておきます。

まずは、カウンターコンポーネントを作成します。
`Counter.vue`

```vuejs

<template>
    <div id="counter">
        <span>{{ num }} </span>
        <button @click="addOne">up</button>
        <button @click="substractOne">down</button>
        <p v-if="num === 0">
            Press 'up' or 'down' button!!!!
        </p>
    </div>
</template>

<script>
    export default {
        name: 'Counter',
        data() {
            return {
                num: 0
            }
        },
        methods: {
            addOne: function () {
                this.num = this.num + 1;
            },
            substractOne: function () {
                this.num = this.num - 1;
            }
        }
    }
</script>

```

`template`はデフォルトでHTMLを記述します。数字とカウントアップ、ダウンのボタンを用意しました。
`@click`の所はボタンが押された際に実行するメソッドを書くことができます。`+ 1`する関数と`- 1`する関数をそれぞれ呼び出します。
`v-if`ディレクティブは条件付きレンダリングと呼ばれるもので、Vue.jsのディレクティブ内の式が`true`を返す場合に描写が行われます。

`script`の`date()`の部分で`num`プロパティを作成し、`methods`の部分ではプラスとマイナスそれぞれのメソッドを作成しています。

作ったコンポーネントを`App.vue`に追加します。

```vuejs

<template>
  <div id="app">
    <img alt="Vue logo" src="./assets/logo.png">
    <HelloWorld msg="Welcome to Your Vue.js App"/>
    <Counter/>
  </div>
</template>

<script>
import HelloWorld from './components/HelloWorld.vue'
import Counter from './components/Counter'

export default {
  name: 'app',
  components: {
    HelloWorld,
    Counter
  }
}

```

そしてDevサーバ起動します。
![counter.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/202545/d0027a25-2988-80a1-05c5-6b2098b13f2b.jpeg)

しょぼいですが、カウンターアプリが作成できました。

# 感想
とりあえずアプリを作って、動かしてみました。
わからんところだらけだけど雰囲気で動くなーって感じました。


# 参考文献
* [Vue.jsドキュメント](https://jp.vuejs.org/)
* [Vue Loaderドキュメント](https://vue-loader-v14.vuejs.org/ja/features/es2015.html)
* [Vue.jsのrender: h => h(App)について調べた](https://qiita.com/teinen_qiita/items/ed1bb1909a17f9ca9daa)