Help us understand the problem. What is going on with this article?

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

More than 1 year has passed since last update.

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 のところが変わってると思います。

おしまい

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

567000
組み込みからWebサービスから機械学習からオブジェクト指向から開発プロセスから認定ScrumMasterからなんならUI/UX/HCD/デザイン思考まで。 投稿した内容でなにかあったらTwitterなどへ気軽にどうぞです。
https://twitter.com/aka567000
Why not register and get more from Qiita?
  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
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  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
ユーザーは見つかりませんでした