Vue.js を vue-cli でシンプルに過不足なくスタートする
注意
現時点(2019.2)で最新である Vue CLI 3系でのチュートリアルは別記事として投稿してあります。
Vue.js を vue-cli を使ってシンプルにはじめてみる
この記事は 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について の記事がありましたのでこちらもおススメです!!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
を見てみます。
<!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
をみてみましょう。
<!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 をいじる
// 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 をいじる
<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つのパートに分割して見てみましょう。
<template>
<div id="app">
<img src="./assets/logo.png">
<HelloWorld/>
</div>
</template>
まず、ここまで。 普通のHTMLです。
(一か所だけ <HelloWorld>
というタグが気になりますが)
次、
<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>
ここは単にスタイル。 この.vue
ファイルには、HTMLのテンプレートとスタイルを一緒にかいておけるということは覚えておきましょう。 いわゆるコンポーネント化ができているということですので。
次に、後回しにした Hello を見てみます。
<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
の中の画像も消してしまいます。
結果、今は、以下の状態です。
<!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>
// 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/>'
})
<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
を下のように書き換えて動作確認してみます。
<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
を書き換えてみます。 だんだんアプリケーションっぽくなってきます。
<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
と出すようにしてみます。
<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
タグがでます。
イベントで処理する
もう少しアプリケーションっぽくしたいので、ボタンでのイベントを作ってみます。 ボタンをおすとテキストがクリアされるようなメソッドを作ります。
<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 }
を追加しています。
<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
を作ってみます。
<template>
<div>
ここはヘッダーです。
</div>
</template>
本当はここにヘッダのHTMLをいろいろ書くのですがとりあえずこれだけ。 これでブラウザを見てみると、無事に、上部に ここはヘッダーです
と出ます。 試しに、<myheader>
タグを何行か連続して書いてみてると……想像通りの結果になると思います。(こういうのは結果がわかっていても試してみるのが大事です!)
もちろんこのmyheader.vue
にもscript
やstyle
も書けます。 なので、ヘッダで扱うアニメーションであったりなどはここにコンポーネント化することができるのです。 少し前だと server side include などでやるのが当たり前でしたが、クライアント側だけでこういうことが簡単にできるのは非常に便利です。
一休み
これでコンポーネント化ができました。次は、最近出てきたvue-router
あたりを使ってページングあたりを目指してみるのが良いのかもしれませんが、比較的SPA(シングルページアプリケーション)は扱いがいろいろ難しいのでやめました。
※ とか言いながらVue-routerについても投稿しました。こちら参照
AJAX的なものと組み合わせてデータ更新をしてみたいと思います。
jQuery::Ajaxと連携
外部のWebAPIを使ってデータを取得して画面上のデータ更新をしてみます。 AJAXなどやろうとした場合、いろいろと選択肢はあるのですが、やっぱりなんだかんだ言って jQuery が一番安定していてノウハウも多いので jQuery でいきたいと思います。
普通に<script>
で読み込まずに、もう少しモダンな方法でいきます。
まず、npmを使ってjQueryモジュールを取得してきます。
$ npm install --save-dev jquery
次に、jQuery 使うためにWebPack
に追加します。
:
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
を修正します。
:
'globals' : {
'$': false,
'jQuery': false
}
:
rules
の後あたりに、globals
を追加します。
これでやっと準備は万端です。
実際に jQuery での処理を追加していきます。 今回は起動時に外部のWebサービスから適当に値を取得して画面を書き換える処理を行います。ボタンクリック時に行いたければ前みたいに@click
でやればいいだけです。
<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
に入れています。
おしまい
これで一通りやりたいことはできました。