Vue.js: マウントのしかた まとめ

Vue.jsでコンポーネント用のオブジェクト（オプションオブジェクト）をマウントする方法のまとめです。

## オプションオブジェクトは別ファイルに分けましょう

[Vue.jsのサイト](https://jp.vuejs.org/v2/guide/#%E5%AE%A3%E8%A8%80%E7%9A%84%E3%83%AC%E3%83%B3%E3%83%80%E3%83%AA%E3%83%B3%E3%82%B0)や入門書では、次のように `new Vue` にオブジェクトを丸ごと入れる書き方が紹介されています。

```javascript
var app = new Vue({
  el: '#app',
  data() {
    return {
      message: 'hello'
    };
  }
});
```

これはよい方法ではありません。実際に運用されるアプリケーションでこの書き方をすると、オブジェクトの内容が長くなったときに、ソースコードが読みづらくなります。

オプションオブジェクトは別のjsファイルまたはvueファイルにしてエクスポート、インポートするようにしましょう。

```html:HTMLファイル
<div id="app"></div>
```

```vue:オプションオブジェクト（vueファイル）
<template>
 <div>
  {{message}}
 </div>
</template>

<script>
export default {
  data() {
    return {
      message: 'hello'
    };
  }
})
</script>
```

```javascript:マウントするJavaScript
import Vue from 'vue';
import App from './app.vue';

new Vue(App).$mount('#app');
```

この書き方をするにはWebpackが必要で面倒ですが、**Vue.jsの学習を始めるときにも、最初からファイルを分ける書き方で行く**のがおすすめです。これは、仕事で作ったプロジェクトに入門用の書き方を持ち込んでしまい、メンテしづらいコードにしてしまったという私の反省です。

学習用にWebpackを使うには、[Vue CLI](https://qiita.com/search?q=Vue+CLI)を使うのが簡単です。Railsの人はWebpackerを使えばいいでしょう。[手作業でスクラッチからWebpackを使う](https://qiita.com/kazutosato/items/ab785414be70e536c267)こともできます。


## マウントの基本形

繰り返しになりますが、オプションオブジェクトをマウントするには、最初は次の形だけ覚えればよいでしょう。

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

new Vue(App).$mount('#app');
```

Vue 3では、次のようになります。`mount`に`$`が付かないことに注意しましょう。

```javascript
import { createApp } from 'vue';
import App from './app.vue';

createApp(App).mount('#app');
```

## テンプレートがHTML内にある場合

VueのテンプレートをHTML内に置く場合も、同じような形になります。オプションオブジェクトのファイルはjsファイルでもいいですし、`<script> ... </script>` だけのvueファイルでもいいです。

```html:HTMLファイル
<div id="app">
  {{message}}
</div>
```

```javascript:オプションオブジェクト（jsファイル）
export default {
  data() {
    return {
      message: 'hello'
    };
  }
})
```

```javascript:マウントするJavaScript
import Vue from 'vue';
import App from './app.js';

new Vue(App).$mount('#app');
```

この場合は、マウント時にHTML内の `<div id="app"> ... </div>` はDOMツリーから削除され、テンプレートを元に作られた新しいdiv要素が埋め込まれます（jQueryであれこれしたいときは、`mounted`メソッドに書かなければならないのはこのためです）。

## elオプション

どのHTML要素にマウントするのかを指定するのに`el`オプションを使うこともできます。`el`オプションがあると、`$mount`メソッドを呼ばなくても自動的にマウントされます。

```javascript:マウントするJavaScript
import Vue from 'vue';
import App from './app.js';

new Vue({ ...App, el: '#app' });
```

Vue 3の`createApp`では`el`オプションが効きません。Vue 3に備えるために、`el`オプションは使わずに上記の基本形を使うとよいでしょう。

## propsを渡す方法

次のようにプロパティ（props）のあるVueコンポーネントもマウントできます。

```vue:オプションオブジェクト（vueファイル）
<template>
  <div>
    <h1 :style="{color: color}">{{message}}</h1>
  </div>
</template>

<script>
export default {
  props: ['color'],
  data() {
    return {
      message: 'hello'
    };
  }
}
</script>
```

マウントするときは、`render`だけのオブジェクトを作り、その中で`h`メソッドの第1引数にインポートしたオブジェクト、第2引数にプロパティを渡す、という形にします。

Vue.jsのマウントのしかたについては、これと上記の基本形の2パターンだけ覚えればいいと思います。

```javascript:マウントするJavaScript
import Vue from 'vue';
import App from './app.js';

new Vue({
  render: h => h(App, { props: { color: 'red' } })
}).$mount('#app');
```

Vue 3では次のようにします。記述量がちょっと減りました。

```javascript:マウントするJavaScript
import { createApp, h } from 'vue';
import App from './app.js';

createApp({
  render: () => h(App, { color: 'red' })
}).mount('#app');
```

## propsDataオプションとVue 3での書き方

上記のようにプロパティを渡すと、親コンポーネント（`render`だけのオブジェクトによる）とその子コンポーネント（インポートしたオブジェクトによる）の2つが作られます。これはちょっと無駄な感じがします。

また、テンプレートのないオブジェクト（HTML内にテンプレートを置く場合など）では、上記の`render`を使ったマウントはできません。

あまり紹介されることのない`propsData`というオプションでプロパティを渡すことができます。この場合はコンポーネントが1つだけで済みます。

```javascript:マウントするJavaScript
import Vue from 'vue';
import App from './app.js';

new Vue({ ...App, propsData: { color: 'red' } }).$mount('#app');
```

ただし、Vue 3の`createApp`では`propsData`が効きません。その代わり、`createApp`の第2引数にプロパティを渡せるようになりました。これは便利。

```javascript:マウントするJavaScript
import { createApp } from 'vue';
import App from './app.js';

createApp(App, { color: 'red' }).mount('#app');
```

## おまけ

引数を渡さずに`$mount`メソッドを読んでもVueコンポーネントができます。この場合、`$el`は親のないDOMオブジェクトとなるので、`appendChild`などで追加します。

この例はRailsの`rails webpacker:install:vue` でできるファイルで見つけました。

```javascript:マウントするJavaScript
import Vue from 'vue';
import App from './app.js';

let app = new Vue(App1).$mount();
document.getElementById('app1').appendChild(app.$el);
```

なお、この方法はVue 3では使えません。