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

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/ をアクセスするとサンプルが表示されます。

image.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.htmlbuid./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 をいじる

./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つのパートに分割して見てみましょう。

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

./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"が入ります。

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

ここまでで、サンプルの動きとしては、index.htmlsrc/App.vuesrc/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')
./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を下のように書き換えて動作確認してみます。

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

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

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も書けます。 なので、ヘッダで扱うアニメーションやメニューの処理であったりなどはここにコンポーネント化することができるのです。 少し前だとSSI(server side include)などでやるのが当たり前でしたが、クライアント側だけでこういうことが簡単にできるのは非常に便利です。

一休み

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

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

Fetchを使ったAjax処理

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

jQueryを使った例はこちらにあります。
Vue-cli2.0版のときの記事の後半で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 = ''
    }
  },
  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 のところが変わってると思います。

おしまい

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

Why do not you register as a user and use Qiita more conveniently?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Linked from these articles
oda3104はじめてVue.jsを触ってみたからリンク
Show old 42 links
Comments
問題のあるコメントを報告する

初めまして、検索で見つけて参考にさせていただいております。

プログラミング初めて1ヶ月ほどで、仕事の関係でvue.jsを使うために勉強しています。
公式HP見ても何がどう動いているのか全く分からず、サンプルプロジェクトをインストールしてもどのファイルがどう作用しているのか理解できず苦しんでいたところ、はじめから説明している本記事に行き着きました。

本記事の序盤で全く表示されないため質問させてください。
「Vue.jsらしくなってきた」あたりで、コードは全く同じに書いているのにブラウザ上では何も表示されません。エラーは出ておりません。
原因としてどこが考えられるのでしょうか?
(ざっくりとした質問ですみません、、、)

問題のあるコメントを報告する

yoshi-kさん
状況がなんともわからないので...ズバリ回答はできませんが...
- 開発者ツール、または、実行してるコンソールでエラーが出てないかを見る
- 最初からやりなおして、1文字修正しては確認、1文字修正しては確認、を繰り返してみてとどこかのステップで表示おかしくなったらそこが原因
なんだかんだで地道にデバッグするのが早いと思います。

何かのお役に立てれば幸いです!

問題のあるコメントを報告する

記事を読んで勉強させてもらっております。

yoshi-kさんの指摘、私も同じでした。
(もう半年経過してますが..)

index.htmlの

  <body>
    <app></app>
  </body>

  <body>
    <div id="body">
      <app></app>
    </div>
  </body>

にして、main.jsの

  el: 'body',

  el: '#body',

にしたら動いたんですが、elプロパティってタグ名を直接
指定することもできるのでしょうか?

問題のあるコメントを報告する

@nakatomodesu さん、コメントありがとうございます!!! すみません、ちょっと確認します。。ご迷惑おかけします。。

問題のあるコメントを報告する

@nakatomodesu @567000

こちらですが、結論から言うと el: 'body' も valid ですし、 el: '#body' も valid となります。

Vue コンポーネントの el へは、CSSのクエリセレクタの文字列または HTMLElement を指定することとなっており、今回の例ですと、el: 'body' の場合は document.body が、 el: '#body' の場合は div#body が対象となっています。

詳しくは日本語公式ドキュメントをご参照ください。

https://jp.vuejs.org/v2/api/#el

このサンプルの場合、意図しているターゲットは div#body であるはずですので、シャープをつけるのが正しいでしょう。

また、実際の開発においては、 document.body 自体にマウントすることはあまり推奨されませんので、そういった意味でも #body にしておくと良いでしょう。

問題のあるコメントを報告する

@potato4d さん、ありがとうございます!!
上記の理屈はよくわかるのですが、動かない理由も教えていただけると助かります。。。bodyタグ指定でも配下のappは探してくれるのでは?という気がしているのですが.....

問題のあるコメントを報告する

567000さん potato4dさん

コメントありがとうございます。

もう一回確認してみたところ、コンソールに以下のようなエラーが出てました。

[Vue warn]: Do not mount Vue to <html> or <body> - mount to normal elements instead.

HTMLタグとBODYタグはあきまへん!という意味ですかね。
普通のはいいよ。って書いてますね。

問題のあるコメントを報告する

ありがとうございます!
最新版で再度手順みなおして本記事もUpしておきます!!
気付かせてくれてありがとうございます! 修正ポイントありがとうございます!!

問題のあるコメントを報告する

いえいえ、1mmでもお役に立ててよかったです。

とても良い記事だと思いますので、これから読まれる方のためにも
是非是非よろしくお願いします!

yoshi-kさんも気付くと良いですねw

問題のあるコメントを報告する

最新版にあわせてソースとコマンドを修正いたしました。
・プロキシが http_proxy ではなく https_proxy が必要だった
・最初のconfigでvue-routerがデフォルト Yes になってたので、n でやってもらう手順に変更
・ が

になってた
・Hello が HelloWorld になってた

他誤字脱字修正

問題のあるコメントを報告する

分かりやすい記事ありがとうございます。

jQueryを使った例はこちらにあります

ここの「こちら」のリンク先がこの記事そのものになっているようです。
編集リクエストを送れば良いかとも思ったのですが、どの記事か分からなかったのでコメントさせて頂きます。

問題のあるコメントを報告する

@gimKondo さん、コメント気付くの遅くなってすみません…!
本当ですねお恥ずかしい。。後ほど修正しておきます。ありがとうございました!

問題のあるコメントを報告する

恐縮ですがコメント失礼します。自分はJavascriptの勉強を始めたばかりなのでわからない部分がありまして、最後のコードにおける
.catch( () => {
});

は例外処理の一部かと思うのですが、なんの為に存在しているのでしょうか?

問題のあるコメントを報告する

@kamol さん、 反応遅くてすみません。。このコード良くないですね。。

エラーがあったときの処理を書くべきですが何もしてない無駄なコードです。混乱させて申し訳ないです。

後ほど修正しておきます!

問題のあるコメントを報告する

Vue.jsについてとても丁寧に解説して頂いて、分かりやすいです。
ファイルを読み込む入れ子構造はじめ、基本的なことも省略せず書かれているので、初学者でもスラスラと読み進めることができます。

Vue.jsの勉強を始めてから、ドキュメントや記事の通りコピペするだけでVue.jsの中身を理解できていないことが不安でしたが、こちらの記事読んでからVue.jsに親しみが持てました。ありがとうございます。

問題のあるコメントを報告する

@kyohei_ai さん、コメントありがとうございます!!そう言っていただけるだけでこの記事書いた意味ありました。励みになります。

Sign up for free and join this conversation.
If you already have a Qiita account