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

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

**注意**
現時点(2019.2)で最新である Vue CLI 3系でのチュートリアルは別記事として投稿してあります。
[Vue.js を vue-cli を使ってシンプルにはじめてみる](https://qiita.com/567000/items/dde495d6a8ad1c25fa43)

この記事は Vue CLI 2版です。

## はじめに
``React.js``や``Angluar``やたくさんあるJavaScriptフレームワーク。けっきょく何を使おうか悩んだ結果一番シンプルにやりたいことができそうな``Vue.js``をやることにしました。
ただ単に``Vue.js``を使うだけではなく、せっかくなのでもっともスタンダードと思われる使い方で使ってみたく``vue-cli``を使ったテンプレート作成からやってみました。

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

> (2018.10追加)
> Vue.js 2.0系からは ready () ではなくcreated () を使わないといけないようですということで、編集リクエストいただいたのでありがとうございました！！ ←今回わたしもまだ確認できてないので……もしうまく出来ない等あったらコメントください！！
>
> (2018.8追加)
> vue-cliのバージョンがあがっていくつか手順通りに行かなくなっていたので見直しました。
> うまくいかないなー とご迷惑おかけした方申し訳ありません。
>
> (2018.6追加)
> わたしはまだ遊べていませんが、[Vue Cli UIについて](https://qiita.com/isihigameKoudai/items/eee3eb6a435675fdfd73) の記事がありましたのでこちらもおススメです！！ ``build``などの部分がUIでできるっぽいです。

## npm をインストールする
してください。

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

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

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

```
Vue CLI 3～
$ vue create my-project

～Vue CLI 2
$ vue init webpack my-project

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

いくつか質問形式で聞かれるので、1つだけ注意していただいて、他はとくに何もなければすべて Enter で進めてください。

**注意： ここではシンプルに説明するために vue-router を使いません**
**なので、Install vue-router? (Y/n) n の設問だけは n にして進めてください。**
**vue-routerを使いたい方は Y でもちろん良いですが下記に示すコードと少し変わってきますので注意してください**

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

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

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

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

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

```index.html
<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width,initial-scale=1.0">
    <title>my-project</title>
  </head>
  <body>
    <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>
  <head>
    <meta charset=utf-8>
    <meta name=viewport content="width=device-width,initial-scale=1">
    <title>my-project</title>
    <link href=/static/css/app.cca059254702f9ed953b7df749673cf4.css rel=stylesheet>
  </head>
  <body>
    <div id=app></div>
    <script type=text/javascript src=/static/js/manifest.2ae2e69a05c33dfc65f8.js></script>
    <script type=text/javascript src=/static/js/vendor.b8580e1294724b76ff58.js></script>
    <script type=text/javascript src=/static/js/app.1dfcdeaedd0476691cb8.js></script>
  </body>
</html>
※みやすいように整形してあります
```

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

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

### main.js をいじる

```./src/main.js
// The Vue build version to load with the `import` command
// (runtime-only or standalone) has been set in webpack.base.conf with an alias.
import Vue from 'vue'
import App from './App'

Vue.config.productionTip = false

/* eslint-disable no-new */
new Vue({
  el: '#app',
  components: { App },
  template: '<App/>'
})
```

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

### App.vue をいじる

```html:./src/App.vue
<template>
  <div id="app">
    <img src="./assets/logo.png">
    <HelloWorld/>
  </div>
</template>

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

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 src="./assets/logo.png">
    <HelloWorld/>
  </div>
</template>
```

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

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

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>
    <h2>Essential Links</h2>
    ...省略...
  </div>
</template>

<script>
export default {
  name: 'HelloWorld',
  data () {
    return {
      msg: 'Welcome to Your Vue.js App'
    }
  }
}
</script>

<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped>
h1, h2 {
  font-weight: normal;
}
ul {
  list-style-type: none;
  padding: 0;
}
li {
  display: inline-block;
  margin: 0 10px;
}
a {
  color: #42b983;
}
</style>

```

だいたい今まで見たファイルと一緒ですが、``{{ msg }}``というのがあります。 後の方で ``export ...`` というのがあって、その中で json 的に ``msg: 'Welcome to Your Vue.js App'`` と返しているのでそこで置き換わるんだろうな、と推測できます。

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

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

```index.html
<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width,initial-scale=1.0">
    <title>my-project</title>
  </head>
  <body>
    <div id="app"></div>
    <!-- built files will be auto injected -->
  </body>
</html>
```

```src/main.js
// The Vue build version to load with the `import` command
// (runtime-only or standalone) has been set in webpack.base.conf with an alias.
import Vue from 'vue'
import App from './App'

Vue.config.productionTip = false

/* eslint-disable no-new */
new Vue({
  el: '#app',
  components: { App },
  template: '<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 dev` を実行して `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 dev``しておけば変更を検知してかってにブラウザの更新まで行われます。 画面では、単に ``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`` という名前によってバインディングされます。 なので、``input``タグの``v-model="msg-hogehoge"``みたいな名前にすれば当然データバインディングはされません。

### データを判定する

入力されたまま表示するだけだと面白くないので、少し条件を加えてみます。 入力されたテキストが空になった時には固定で``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``も書けます。 なので、ヘッダで扱うアニメーションであったりなどはここにコンポーネント化することができるのです。  少し前だと server side include などでやるのが当たり前でしたが、クライアント側だけでこういうことが簡単にできるのは非常に便利です。

## 一休み

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

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

## jQuery::Ajaxと連携

外部のWebAPIを使ってデータを取得して画面上のデータ更新をしてみます。 AJAXなどやろうとした場合、いろいろと選択肢はあるのですが、やっぱりなんだかんだ言って jQuery が一番安定していてノウハウも多いので jQuery でいきたいと思います。

普通に``<script>``で読み込まずに、もう少しモダンな方法でいきます。

まず、npmを使ってjQueryモジュールを取得してきます。

```
$ npm install --save-dev jquery
```

次に、jQuery 使うために``WebPack``に追加します。

```build/webpack.dev.conf.js
  :
module.exports = merge(baseWebpackConfig, {
  module: {
    loaders: utils.styleLoaders({ sourceMap: config.dev.cssSourceMap })
  },
  // eval-source-map is faster for development
  devtool: '#eval-source-map',
  plugins: [
    new webpack.ProvidePlugin({
      $: 'jquery',
      jQuery: 'jquery'
    }),
  :
```

``plugins`` の個所に ``webpack.ProvidePlugin`` として、jQueryを追加します。
もし ``npm run dev`` が動いているなら、一回止めて再起動してください。

次に jQuery の実装をしていくのですが、この環境ではかなり厳密な Lint が動いているのでエラーがあるとビルドしてくれません。そこで、エラーにならないように、jQuery関連を許可する設定をしておきます。 具体的には ``.eslintrc.js`` を修正します。

```.eslintrc.js
  :
  'globals' : {
    '$': false,
    'jQuery': false
  }
  :
```

``rules``の後あたりに、``globals``を追加します。
これでやっと準備は万端です。

実際に 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 () {
    var that = this
    $.getJSON('http://www.geonames.org/postalCodeLookupJSON?postalcode=10504&country=US&callback=?', {}, function (json) {
      console.log(json)
      that.msg = json.postalcodes[0].adminName1
    })
  }
}
</script>
```

だんだん長くなってきた ``App.vue`` ですが、今回は、``created()``を追加しています。 中身は比較的簡単に（なぜかアメリカの）郵便番号検索で住所を取得しています。 ここまで来ると普通の jQuery なので説明不要と思いますが、JSONデータを取得してきて、値を ``msg`` に入れています。

## おしまい

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