Vue.js を vue-cli でシンプルに過不足なくスタートする
はじめに
可能なかぎり、この通りやればできるようにシンプルで過不足なくコマンドをまとめていきます。
(2019.02)
Vue CLI 3対応するためにほぼサンプルコードを一新しました。
Vue-cli 2.x版が必要なかたはこちらにバックアップしておいたのでご参照ください。
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/
をアクセスするとサンプルが表示されます。
プロジェクトを改造しながら理解する
全体の流れを理解する
出来上がったソースを見てみると非常に様々なものが作られているのがわかります。 実際に動いているソースを見るためにビルドを行います。 理解するためには、ちょっと手を入れて、ちょっと動きがかわって、を繰り返すのが一番です。
まず出来上がった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
をみてみましょう。
<!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 をいじる
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 をいじる
<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つのパートに分割して見てみましょう。
<template>
<div id="app">
<img alt="Vue logo" src="./assets/logo.png">
<HelloWorld msg="Welcome to Your Vue.js App"/>
</div>
</template>
まず、ここまで。 普通のHTMLです。
(一か所だけ <HelloWorld>
というタグが気になりますが)
次、
<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
ファイルには、HTMLのテンプレートとスタイルを一緒にかいておけるということは覚えておきましょう。 いわゆるコンポーネント化ができているということですので。
次に、後回しにした Hello を見てみます。
<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"
が入ります。
:
<HelloWorld msg="Welcome to Your Vue.js App"/>
:
ここまでで、サンプルの動きとしては、index.html
← src/App.vue
← src/components/HelloWorld.vue
という入れ子構造になっていることがなんとなくわかったのですが、ややこしいので1階層だけにしてみましょう。 App.vue
をシンプルにして、Hello
も削除してしまいます。 ついでに、assets
の中の画像も消してしまいます。
結果、今は、以下の状態です。
<!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>
import Vue from 'vue'
import App from './App.vue'
Vue.config.productionTip = false
new Vue({
render: h => h(App),
}).$mount('#app')
<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
を下のように書き換えて動作確認してみます。
<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
を書き換えてみます。 だんだんアプリケーションっぽくなってきます。
<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
と出すようにしてみます。
<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
も書けます。 なので、ヘッダで扱うアニメーションやメニューの処理であったりなどはここにコンポーネント化することができるのです。 少し前だとSSI(server side include)などでやるのが当たり前でしたが、クライアント側だけでこういうことが簡単にできるのは非常に便利です。
一休み
これでコンポーネント化ができました。次は、最近出てきたvue-router
あたりを使ってページングあたりを目指してみるのが良いのかもしれませんが、比較的SPA(シングルページアプリケーション)は扱いがいろいろ難しいのでやめました。
※ とか言いながらVue-routerについても投稿しました。こちら参照
AJAX的なものと組み合わせてデータ更新をしてみたいと思います。
Fetchを使ったAjax処理
外部のWebAPIを使ってデータを取得して画面上のデータ更新をしてみます。
今回は標準で手軽に使えるfetch
を使います。
jQueryを使った例はこちらにあります。
Vue-cli2.0版のときの記事の後半で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 () {
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
のところが変わってると思います。
おしまい
これで一通りやりたいことはできました。