Vue.js初心者向け：インラインテンプレートから単一ファイルコンポーネントの使い方まで

# はじめに

Vue.js でテンプレートを使うのに「単一ファイルコンポーネント」という便利なものがあります。しかし公式ドキュメントを読んでも導入がよくわからなかったので、備忘録としてまとめました。

誤字・脱字・勘違いなどありましたらご指摘ください。

## 前提条件

- Webpack がインストールしてある
- Babel がインストールしてあり、ES2015設定がしてある

# ①一番基本的な Vue.js の使い方

Vue.js を CDN から持ってきます。Vue はグローバルなオブジェクトになっているので前置きも不要で呼び出せます。

```js:javascript
<script src="https://unpkg.com/vue"></script>
<script>
  new Vue = ({
    el: '#hoge',
    data: {
      list: [
        { name: '山田' },
        { name: '田中' },
        { name: '鈴木' }
      ]
    }
  });
</script>
```

```html:html
<div id="hoge">
  <ul>
    <li v-for="item in list">{{item.name}}</li>
  </ul>
</div>
```

```html:結果
<div id="hoge">
  <ul>
    <li>山田</li>
    <li>田中</li>
    <li>鈴木</li>
  </ul>
</div>
```

# ②HTMLにインラインでテンプレートを記述して子コンポーネントを使う方法

## 子コンポーネントは、親のデータを加工したい時などに使える

親が持っているデータをそのまま表示するなら前述の簡単な方法でかまいません。ちょっと加工したい時は子コンポーネントを使うと切り分けがしやすくなります。

## 1ページでしか使わないならインラインテンプレートがおすすめ

後述する「単一ファイルコンポーネント」は便利ですが**そのページでしか使わないもの**のためにファイルを切り出すのも少々面倒です。小規模かつ静的なページならHTML内にテンプレートを記述するインラインテンプレートがおすすめです。

インラインテンプレートの記述には `<script type="text/x-template" id="固有ID">` が使えます。

```html:html
<div id="hoge">
  <!-- v-bind でコンポーネントにデータを渡す -->
  <!-- member-data はタグ内ではケバブケースだが、定義部ではキャメルケース -->
  <member-tag v-for="item in list" v-bind:member-data="item"></member-tag>
</div>

<script type="text/x-template" id="compo-member">
  <div>
    <div class="name">{{memberData.name}}</div>
    <!-- 年齢は HTML が含まれる予定なので v-html を使う -->
    <div class="age" v-html="age"></div>
  </div>
</script>
```

```js:javascript
// データ定義
var data = {
  list: [
    { name:'山田', age:'30歳' },
    { name:'田中', age:'29歳' },
    { name:'鈴木', age:'25歳' }
  ]
};

// コンポーネント定義
var compo_member = {
  // id="compo-member" のタグの中身を取得
  template: '#compo-member',
  // 必要なデータを持たせる準備。型の定義もする。
  // ここではキャメルケースを用いる
  props: {
    memberData: Object
  },
  computed: {
    // 数字だけ <span></span> で囲む
    age: function(){
      var age = this.memberData.age.replace(/(\d+)/g, "<span>$1</span>");
      return age;
    }
  }
}

// Vue.js インスタンス
new Vue = ({
  el: '#hoge',
  data: data,
  components: {
    // コンポーネントの登録。左辺がタグ名になる
    'menber-tag': compo_member
  }
});
```

```html:結果
<div id="hoge">
  <div>
    <div class="name">山田</div>
    <div class="age"><span>30</span>歳</div>
  </div>
  ...
</div>
```

正直、このレベルの加工なら Vue.js インスタンスの定義で methods: を使う方法もあります。しかしコンポーネントとして切り出すことで問題の切り分けや、別のコンポーネントを追加したい時に複雑にならずにすみます。


# ③単一コンポーネントファイルを使って完全に分離する方法

JS もテンプレートの定義も、さらには css まで分離できるのが単一コンポーネントファイル（拡張子.vue）です。JS のモジュールのような感覚で記述できるのでメインのスクリプトをシンプルなまま保つことができます。

## プリコンパイルのために Webpack が必要になる

ここからは Vue.js を CDN版から npm版になります。分割された `.vue` を結合するためには Webpack も必要になります（Browserify でもかまいません）。

この記事では Webpack が導入されている前提なので、追加モジュールのみ記載します。

環境をまとめて構築してくれる `VueCLI` は便利ですがここでは使いません。既存プロジェクトの組み込みには変更箇所のみを把握したほうが後々良いと個人的に考えてます。

## 必要な npm のインストール

```bash:
$ npm install --save-dev vue
$ npm install --save-dev vue-loader
$ npm install --save-dev vue-template-compiler
```

## webpack.config.js の変更

```js:webpack.config.js
    module:{
        loaders: [
            // .vue ファイルを組み込むためのモジュール
            {
                test: /\.vue$/,
                use: 'vue-loader'
            }
        ]
    },
    resolve: {
        extensions: ['.js', '.vue'],
        modules: [
            "node_modules"
        ],
        alias: {
            // vue.js のビルドを指定する
            vue: 'vue/dist/vue.common.js'
        }
    },
    // 〜略〜
```

インストール先の `node_modules/vue/dist/` を見るといくつかの `vue.*.js` があります。フルセット版、ランタイム版などが入っていますが、よくわからない場合は `vue.common.js` を選んでおけば問題ありません。

### 個人的ハマりポイント①

node_modules の中を見たら `vue-template-es2015-compiler` が入っていたのですが`Module build failed: Error: Cannot find module 'vue-template-compiler'` というエラーが出ました。別途 `vue-template-compiler` をインストールすることで解消されました。

### 個人的ハマりポイント②

`[Vue warn]: You are using the runtime-only build of Vue where the template option is not available. Either pre-compile the templates into render functions, or use the compiler-included build.` というエラーが表示されたのですが、Webpack で `alias` を指定することで解決しました。


## ページの一部分を .vue にする場合

方法②のような使い方で、コンポーネント部分のみを別ファイルの `.vue` にする方法です。

```vue:member-tag.vue
<template>
  <section>
    <div class="name">{{memberData.name}}</div>
    <div class="age" v-html="age"></div>
  </section>
</template>

<script>
  module.exports = {
    props: {
      memberData: Object
    },
    computed: {
      age: function(){
        var age = this.memberData.age.replace(/(\d+)/g, "<span>$1</span>");
        return age;
      }
    }
  };
</script>
```

```html:html
<div id="hoge">
  <member-tag v-for="item in list" v-bind:member-data="item"></member-tag>
</div>
```

```js:javascript
import Vue from 'vue';
import MemberTag from '../vue/member-tag.vue';

// データ定義は方法②と同じなので省略

// Vue.js インスタンス
new Vue = ({
  el: '#hoge',
  data: data,
  components: {
    'member-tag': MemberTag
  }
});
```

結果も方法②と同じなので省略します。

ベースになる HTML からテンプレート構文が消えてだいぶスッキリしました。JS もコンポーネント定義が消え、 `import` で読み込むだけになっています。

## ページ全体を .vue にする方法

ページ全体というと語弊がありますね。`<div id="hoge"></div>` の部分をまるっと差し換える方法です。


```vue:app.vue
<template>
  <main id="app">APP</main>
</template>

<script>
module.exports = { };
</script>
```

```html:html
<div id="app">
  <member-tag v-for="item in list" v-bind:member-data="item"></member-tag>
</div>
```

```js:javascript
import Vue from 'vue';
import App from '../vue/app.vue';

// Vue.js インスタンス
new Vue = ({
  el: '#app',
  template: '<App/>',
  components: { App: App }
});
```

↓

```html:結果
<main id="app">APP</main>
```

元の HTML では `<div id="app">〜` となっていたのに中身も含めて完全に差し替わっています。

筆者自身は中規模以上の開発経験がないのでそのへんはよくわかりませんが、作業分担をするような場面ではこちらのほうが利点があるのでしょう。