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

  • 92
    いいね
  • 0
    コメント

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

はじめに

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

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

npm をインストールする

してください。

vue-cli のインストール

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

$ npm install -g vue-cli

プロジェクトを作成する

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

いくつか質問形式で聞かれるのでとくに何もなければすべて Enter で進めてください。

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

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

プロジェクトを改造しながら理解する

全体の流れを理解する

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

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

index.html
<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <title>my-project</title>
  </head>
  <body>
    <app></app>
    <!-- built files will be auto injected -->
  </body>
</html>

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

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

$ npm run build

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

/dist/index.html
<!DOCTYPE html>
<html>
  <head>
    <meta charset=utf-8>
    <title>my-project</title>
    <link href=/static/css/app.9fd4d05a8872f70e7f7ae81b77aaaf84.css rel=stylesheet>
  </head>
  <body>
    <app></app>
    <script type=text/javascript src=/static/js/manifest.f50981b58f9f3dbca4ce.js></script>
    <script type=text/javascript src=/static/js/vendor.9ecb1ec17a626fd34390.js></script>
    <script type=text/javascript src=/static/js/app.7133a01356952d92385e.js></script>
  </body>
</html>
※みやすいように整形してあります

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

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

main.js をいじる

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

/* eslint-disable no-new */
new Vue({
  el: 'body',
  components: { App }
})

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

App.vue をいじる

./src/App.vue
<template>
  <div id="app">
    <img class="logo" src="./assets/logo.png">
    <hello></hello>
    <p>
      Welcome to your Vue.js app!
    </p>
    <p>
      To get a better understanding of how this boilerplate works, check out
      <a href="http://vuejs-templates.github.io/webpack" target="_blank">its documentation</a>.
      It is also recommended to go through the docs for
      <a href="http://webpack.github.io/" target="_blank">Webpack</a> and
      <a href="http://vuejs.github.io/vue-loader/" target="_blank">vue-loader</a>.
      If you have any issues with the setup, please file an issue at this boilerplate's
      <a href="https://github.com/vuejs-templates/webpack" target="_blank">repository</a>.
    </p>
    <p>
      You may also want to checkout
      <a href="https://github.com/vuejs/vue-router/" target="_blank">vue-router</a> for routing and
      <a href="https://github.com/vuejs/vuex/" target="_blank">vuex</a> for state management.
    </p>
  </div>
</template>

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

export default {
  components: {
    Hello
  }
}
</script>

<style>
html {
  height: 100%;
}

body {
  display: flex;
  align-items: center;
  justify-content: center;
  height: 100%;
}

#app {
  color: #2c3e50;
  margin-top: -100px;
  max-width: 600px;
  font-family: Source Sans Pro, Helvetica, sans-serif;
  text-align: center;
}

#app a {
  color: #42b983;
  text-decoration: none;
}

.logo {
  width: 100px;
  height: 100px
}
</style>

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

<template>
  <div id="app">
    <img class="logo" src="./assets/logo.png">
    <hello></hello>
    <p>
      Welcome to your Vue.js app!
    </p>
    <p>
      To get a better understanding of how this boilerplate works, check out
      <a href="http://vuejs-templates.github.io/webpack" target="_blank">its documentation</a>.
      It is also recommended to go through the docs for
      <a href="http://webpack.github.io/" target="_blank">Webpack</a> and
      <a href="http://vuejs.github.io/vue-loader/" target="_blank">vue-loader</a>.
      If you have any issues with the setup, please file an issue at this boilerplate's
      <a href="https://github.com/vuejs-templates/webpack" target="_blank">repository</a>.
    </p>
    <p>
      You may also want to checkout
      <a href="https://github.com/vuejs/vue-router/" target="_blank">vue-router</a> for routing and
      <a href="https://github.com/vuejs/vuex/" target="_blank">vuex</a> for state management.
    </p>
  </div>
</template>

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

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

export default {
  components: {
    Hello
  }
}
</script>

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

<style>
html {
  height: 100%;
}

body {
  display: flex;
  align-items: center;
  justify-content: center;
  height: 100%;
}

#app {
  color: #2c3e50;
  margin-top: -100px;
  max-width: 600px;
  font-family: Source Sans Pro, Helvetica, sans-serif;
  text-align: center;
}

#app a {
  color: #42b983;
  text-decoration: none;
}

.logo {
  width: 100px;
  height: 100px
}
</style>

ここは単にスタイル。 この.vueファイルには、HTMLのテンプレートとスタイルを一緒にかいておけるということは覚えておきましょう。 いわゆるコンポーネント化ができているということですので。

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

./src/components/Hello.vue
<template>
  <div class="hello">
    <h1>{{ msg }}</h1>
  </div>
</template>

<script>
export default {
  data () {
    return {
      // note: changing this line won't causes changes
      // with hot-reload because the reloaded component
      // preserves its current state and we are modifying
      // its initial state.
      msg: 'Hello World!'
    }
  }
}
</script>

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

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

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

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

index.html
<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <title>my-project</title>
  </head>
  <body>
    <app></app>
  </body>
</html>
src/main.js
import Vue from 'vue'
import App from './App'

/* eslint-disable no-new */
new Vue({
  el: 'body',
  components: { App }
})
src/App.vue
<template>
  <div>
    <p>
      サンプル
    </p>
  </div>
</template>

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

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

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

一休み

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

Vue.js らしくしてみる

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

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

./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を書き換えてみます。 だんだんアプリケーションっぽくなってきます。

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と出すようにしてみます。

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 textPタグがでます。

イベントで処理する

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

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 になります。

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

一休み

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

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

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

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

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

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 を作ってみます。

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

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

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

一休み

これでコンポーネント化ができました。次は、最近出てきたvue-routerあたりを使ってページングあたりを目指してみるのが良いのかもしれませんが、比較的SPA(シングルページアプリケーション)は扱いがいろいろ難しいのでやめました。

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でやればいいだけです。

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 = ''
    }
  },
  ready () {
    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 ですが、今回は、ready()を追加しています。 中身は比較的簡単に(なぜかアメリカの)郵便番号検索で住所を取得しています。 ここまで来ると普通の jQuery なので説明不要と思いますが、JSONデータを取得してきて、値を msg に入れています。

おしまい

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