Vue.js を vue-cli を使ってシンプルにはじめてみる

# Vue.js を vue-cli でシンプルに過不足なくスタートする

## はじめに
可能なかぎり、この通りやればできるようにシンプルで過不足なくコマンドをまとめていきます。

> (2019.02)
> Vue CLI 3対応するためにほぼサンプルコードを一新しました。
> Vue-cli 2.x版が必要なかたは[こちら](https://qiita.com/567000/items/81fb5e2db6cd8e9610eb)にバックアップしておいたのでご参照ください。

## vue-cli のインストール
``Vue.js``を使う環境を準備するためのコマンドラインインタフェースをインストールします。

```
$ npm install -g @vue/cli
```

もし、2.xがインストールされている方であればいったんアンインストールしてからインストールしてください。

```
$ npm uninstall -g vue-cli
$ npm install -g @vue/cli
```

## プロジェクトを作成する

```
$ vue create my-project
※ ちなみにプロキシを使う場合には環境変数 ``https_proxy`` を設定してください
```

質問形式で設定を聞かれますがとくにこだわりがなければ Enter で進めてください。

```
$ cd my-project
$ npm run serve
```

ブラウザで ``http://localhost:8080/`` をアクセスするとサンプルが表示されます。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/33095/5294d835-dd18-a6b5-eb08-dc35b307f632.png)



## プロジェクトを改造しながら理解する
### 全体の流れを理解する

出来上がったソースを見てみると非常に様々なものが作られているのがわかります。 実際に動いているソースを見るためにビルドを行います。 理解するためには、ちょっと手を入れて、ちょっと動きがかわって、を繰り返すのが一番です。

まず出来上がった``public/index.html``を見てみます。

```public/index.html
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width,initial-scale=1.0">
    <link rel="icon" href="<%= BASE_URL %>favicon.ico">
    <title>my-project</title>
  </head>
  <body>
    <noscript>
      <strong>We're sorry but my-project doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
    </noscript>
    <div id="app"></div>
    <!-- built files will be auto injected -->
  </body>
</html>
```

あやしいのは``<div id="app"></div>``ですが、これの正体はどこで定義されているのでしょうか？ よく見てみると ``<script>`` の読み込みもありません。 ということは、実際動いているHTMLはこれではありません。

ということで、実際に動いているソースを見るためにビルドを行います。

```
$ npm run build
```

すると ``./dist`` というフォルダが作られその中に実際に動くソースが作られます。 さらに言えば ``./dist/index.html`` が実際に最初に動くソースになります。 それでは``./dist/index.html``をみてみましょう。

```./dist/index.html
<!DOCTYPE html>
<html lang=en>

<head>
    <meta charset=utf-8>
    <meta http-equiv=X-UA-Compatible content="IE=edge">
    <meta name=viewport content="width=device-width,initial-scale=1">
    <link rel=icon href=/favicon.ico> <title>my-project</title>
    <link href=/css/app.e2713bb0.css rel=preload as=style>
    <link href=/js/app.81951a0b.js rel=preload as=script>
    <link href=/js/chunk-vendors.7ada56f9.js rel=preload as=script>
    <link href=/css/app.e2713bb0.css rel=stylesheet>
</head>

<body><noscript><strong>We're sorry but my-project doesn't work properly without JavaScript enabled. Please enable it
            to continue.</strong></noscript>
    <div id=app></div>
    <script src=/js/chunk-vendors.7ada56f9.js> </script> <script src=/js/app.81951a0b.js> </script> </body> </html>
※読みやすいように整形してあります
```

もとのソースと比較してcssやscriptが入っているのがわかります。

ということでこれが大枠の動きです。 ``index.html`` → ``build`` → ``./dist/index.html`` です。 それがわかったところでソースをいじっていきます。

### main.js をいじる

```./src/main.js
import Vue from 'vue'
import App from './App.vue'

Vue.config.productionTip = false

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

`'#app'`に対して何かしているようですがよくわかならいのでスルーします。 ``./App``を import しているようなので、次は、``./App``を見てみましょう。

### App.vue をいじる

```html:./src/App.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>
```

ちょっと長いですが、3つのパートに分割して見てみましょう。

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

まず、ここまで。 普通のHTMLです。
（一か所だけ ``<HelloWorld>`` というタグが気になりますが）
次、

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

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

ここは残して、後で考えることにして、次。

```html
<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``ファイルには、HTMLのテンプレートとスタイルを一緒にかいておけるということは覚えておきましょう。 いわゆるコンポーネント化ができているということですので。

次に、後回しにした Hello を見てみます。

```html:./src/components/HelloWorld.vue
<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>
```

だいたい今まで見たファイルと一緒ですが、``{{ msg }}``というのがあります。 後の方で ``export ...`` というのがあって、その中で json 的に ``msg: String`` と宣言しています。

ではこの`msg`には何が入るのかというと。`App.vue`の中で以下のようになっていたのを思い出してみます。おそらく想像できる通りですがここで与えた`"Welcome to Your Vue.js App"`が入ります。

```html:./src/App.vue
       :
    <HelloWorld msg="Welcome to Your Vue.js App"/>
       :
```

ここまでで、サンプルの動きとしては、``index.html`` ← ``src/App.vue`` ← ``src/components/HelloWorld.vue`` という入れ子構造になっていることがなんとなくわかったのですが、ややこしいので1階層だけにしてみましょう。 ``App.vue``をシンプルにして、``Hello`` も削除してしまいます。 ついでに、``assets`` の中の画像も消してしまいます。

結果、今は、以下の状態です。

```./public/index.html
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width,initial-scale=1.0">
    <link rel="icon" href="<%= BASE_URL %>favicon.ico">
    <title>my-project</title>
  </head>
  <body>
    <noscript>
      <strong>We're sorry but my-project doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
    </noscript>
    <div id="app"></div>
    <!-- built files will be auto injected -->
  </body>
</html>
```

```./src/main.js
import Vue from 'vue'
import App from './App.vue'

Vue.config.productionTip = false

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

```html:./src/App.vue
<template>
  <div>
    <p>
      サンプル
    </p>
  </div>
</template>
```

他にも`test`やなんやらありますが基本的にはこの3ファイルで構成されています。

* index.html ... HTMLの親玉の部分。``head``のいろいろいじる以外は特に修正の必要なし
* src/main.js ... しばらく変更の必要なさそう
* src/App.vue ... 主に変更するファイル？

です。 比較的シンプルにおさまりました。

ここらで一回 `npm run serve` を実行して `http://localhost:8080` を見てみてください。 シンプルに サンプル とだけ出た画面が出ると思います。

## 一休み

ということで、いったん、シンプルな構造に落とし込むことができました。 ただ、これでは、いったい Vue.js の何がよいのかわかりません。 次は、Vue.js をいかしたサンプルに改造することを考えたいと思います。

## Vue.js らしくしてみる

### データバインディングを体験してみる

Vue.jsらしくデータバインディングをしてみます。つまり簡単に言うと、データと表示を連携（バインド）させてみます。
``App.vue``を下のように書き換えて動作確認してみます。 

```html:./src/App.vue
<template>
  <div>
    <p>
      {{msg}}
    </p>
  </div>
</template>

<script>
export default {
  data () {
    return {
      msg: 'Hello World!'
    }
  }
}
</script>
```

``npm run serve``しておけば変更を検知してかってにブラウザの更新まで行われます。 画面では、単に ``P``タグで、``Hello World!`` と出てると思います。 どうしてかというと``{{msg}}``のところに、``export default{ data() }``で渡している ``msg`` の値がはいるからです。

もちろんソースコード上で ``Hello World!`` を書き換えれば同じように画面も変わるのですが、フォーム``<input>``を使ってリアルタイムに書き換えてみましょう。 ``App.vue``を書き換えてみます。 だんだんアプリケーションっぽくなってきます。

```html:src/App.vue
<template>
  <div>
    <p>
      {{msg}}
    </p>
    <input type="text" v-model="msg">
  </div>
</template>

<script>
export default {
  data () {
    return {
      msg: 'Hello World!'
    }
  }
}
</script>
```

画面ではテキストボックスが追加されました。 ここでテキストボックスをいじると、表示も変わります。 特にコールバックなども指定していないのにこういうことができるのが、いわゆる、データバインディングのすごさです。 なんとなくわかると思いますがすべてが ``msg`` という名前によってバインディングされます。 

### データを判定する

入力されたまま表示するだけだと面白くないので、少し条件を加えてみます。 入力されたテキストが空になった時には固定で``no text``と出すようにしてみます。

```html:src/App.vue
<template>
  <div>
    <p v-if="msg.length > 0">
      {{msg}}
    </p>
    <p v-else>
      no text
    </p>
    <input type="text" v-model="msg">
  </div>
</template>

<script>
export default {
  data () {
    return {
      msg: 'Hello World!'
    }
  }
}
</script>
```

おわかりでしょうか。 ``p``タグのところに``v-if``が加わり文字長のチェックをしています。文字長があれば普通に表示、そうじゃなければ（つまり文字長ゼロ）``v-else``の処理に入って ``no text`` の ``P``タグがでます。

### イベントで処理する

もう少しアプリケーションっぽくしたいので、ボタンでのイベントを作ってみます。 ボタンをおすとテキストがクリアされるようなメソッドを作ります。

```html:src/App.vue
<template>
  <div>
    <p v-if="msg.length > 0">
      {{msg}}
    </p>
    <p v-else>
      no text
    </p>
    <input type="text" v-model="msg">
    <button @click="clear()">clear</button>
  </div>
</template>

<script>
export default {
  data () {
    return {
      msg: 'Hello World!'
    }
  },
  methods: {
    clear () {
      this.msg = ''
    }
  }
}
</script>
```

ボタン``<button>``を追加してクリックで``clear()``がよびだされるようにします。``@click="clear()"`` です。 呼び出される関数の実体は、``methods``内に定義してあります。非常にシンプルに``msg=''``で空にしているだけです。

ブラウザ上で見るとボタンが出ているはずです。 ボタンを押すと、テキストボックスが空になって、表示が ``no text`` になります。

動きとしては、``@click`` で ``clear()`` が呼び出されて　``msg=''`` で ``msg`` が空になって データバインディングの結果表示が更新されて ``v-if`` で文字長がゼロなので ``no text`` になります。

## 一休み

これで Vue.js を使って、データバインディングと簡単なイベント処理をすることができて Vue.js っぽくなってきました。次はもう少しコンポーネント的な使い方を考えてみます。

## 少し便利にコンポーネント化を体験する

通常どんなページで共通化されるであろうヘッダ部分をコンポーネントにしてみます。

### ヘッダの呼び出し部分を作る

さっそく追加。 まずは``template``に``<myheader>``というタグを作ってみます。名前は適当です。そして``<script>``の最初のところに``import myheader from './components/myheader'``を追加します。 ``export default`` の箇所にも ``components: { myheader }`` を追加しています。

```html:src/App.vue
<template>
  <div>
    <myheader></myheader>
    <p v-if="msg.length > 0">
      {{msg}}
    </p>
    <p v-else>
      no text
    </p>
    <input type="text" v-model="msg">
    <button @click="clear()">clear</button>
  </div>
</template>

<script>
import myheader from './components/myheader'

export default {
  components: {
    myheader
  },
  data () {
    return {
      msg: 'Hello World!'
    }
  },
  methods: {
    clear () {
      this.msg = ''
    }
  }
}
</script>
```

追加したものにあわせて ``src/components`` 配下に ``myheader.vue`` を作ってみます。

```html:src/components/myheader.vue
<template>
  <div>
    ここはヘッダーです。
  </div>
</template>
```

本当はここにヘッダのHTMLをいろいろ書くのですがとりあえずこれだけ。 これでブラウザを見てみると、無事に、上部に ``ここはヘッダーです`` と出ます。 試しに、``<myheader>``タグを何行か連続して書いてみてると……想像通りの結果になると思います。（こういうのは結果がわかっていても試してみるのが大事です！）

もちろんこの``myheader.vue``にも``script``や``style``も書けます。 なので、ヘッダで扱うアニメーションやメニューの処理であったりなどはここにコンポーネント化することができるのです。  少し前だとSSI（server side include）などでやるのが当たり前でしたが、クライアント側だけでこういうことが簡単にできるのは非常に便利です。

## 一休み

これでコンポーネント化ができました。次は、最近出てきた``vue-router``あたりを使ってページングあたりを目指してみるのが良いのかもしれませんが、比較的SPA（シングルページアプリケーション）は扱いがいろいろ難しいのでやめました。
※ とか言いながらVue-routerについても投稿しました。[こちら参照](https://qiita.com/567000/items/d6a7c694a370dc92e774)

AJAX的なものと組み合わせてデータ更新をしてみたいと思います。

## Fetchを使ったAjax処理

外部のWebAPIを使ってデータを取得して画面上のデータ更新をしてみます。
今回は標準で手軽に使える`fetch`を使います。

> jQueryを使った例は[こちら](https://qiita.com/567000/items/81fb5e2db6cd8e9610eb)にあります。
> Vue-cli2.0版のときの記事の後半でjQueryを使っていますのでそちらご参照ください。

今回は起動時に外部のWebサービスから適当に値を取得して画面を書き換える処理を行います。もしボタンクリック時に行いたければ前みたいに``@click``に書いてあげればいいだけです。

```html:src/App.vue
<template>
  <div>
    <myheader></myheader>
    <p v-if="msg.length > 0">
      {{msg}}
    </p>
    <p v-else>
      no text
    </p>
    <input type="text" v-model="msg">
    <button @click="clear()">clear</button>
  </div>
</template>

<script>
import myheader from './components/myheader'

export default {
  components: {
    myheader
  },
  data () {
    return {
      msg: 'Hello World!'
    }
  },
  methods: {
    clear () {
      this.msg = ''
    }
  },
  created () {
    fetch('http://www.geonames.org/postalCodeLookupJSON?postalcode=10504&country=US')
    .then( response => {
      return response.json()
    })
    .then( json => {
      this.msg = json.postalcodes[0].adminName1
    })
    .catch( (err) => {
      this.msg = err // エラー処理
    });
  }
}
</script>
```

だんだん長くなってきた ``App.vue`` ですが、今回は、``created()``を追加しています。

中身は比較的簡単に（なぜかアメリカの）郵便番号検索で住所を取得しています。FetchでJSONデータを取得してきて値を ``msg`` に入れています。

実行すると `msg` のところが変わってると思います。

## おしまい

これで一通りやりたいことはできました。