Rails
vue.js
webpack

Vue.jsのコンポーネント化の方法まとめ(rails+webpacker+vue.js)

背景

vue.jsを最大限活用するためには各部品をうまくコンポーネント化することが大切だと思ったが、コンポーネント化のやり方がたくさんありすぎて混乱してしまった。
そこで、一度、コンポーネント化の方法を一覧にして、その中から自分に合うやり方をまとめようと思いこの記事を書いている。

ただし、すべてのやり方をまとめるわけではないので、他にもたくさんの書き方があると思います。

参考サイト

前提

環境

ruby 2.4.3
rails 5.1.6
webpacker 3.1.1
vue 2.5.16

構成

今回まとめる方法はwebpackを利用することを前提する。
また、コンポーネント化の構造について理解することを目的とするため、バインディングなどは利用せず、<p>hello template!</p><hello-temp></hello-temp>としてコンポーネント化するという単純なものにする。

テストするファイルの構成は今回使用するものに限定すると以下のようになる。

/app
 ├ /javascript
 │  ├/components
 │  │  ├ /app.vue
 │  │  └ /hello_temp.vue
 │  │  
 │  └ /packs
 │     ├ /application.js
 │     ├ /app.js
 │     └ /hello_temp.js  
 └ /view
    └ /home
    │  └ /top.html.erb
    └ /layouts
       └ /application.html.erb

出力結果はapp/view/home/top.html.erbに行う。
なお、controllerやroutingは事前に準備してある。

テンプレート化方法の概要

  1. hello_temp.jsのみを使う
  2. hello_temp.jsとhello_temp.vueを使う
  3. hello_temp.jsとhello_temp.vueとapp.jsとapp.vueを使う。

1の方法は普通のコンポーネント化(名称不明)と呼ばれ、
2と3の方法は単一ファイルコンポーネント化と呼ばれるらしい。
それぞれの方法の中にも以下の様にいろいろな書き方がある。

A. Vue.componentnew Vue()の両方を使う方法
B. new Vue()のみで書く方法
C. export default {components: {}}を使う方法(vueファイルでインポートする場合を使うのみ)

などなど。

テンプレート化方法一覧

上記のリストに沿ってまとめる。
出力先のapp/view/home/top.html.erbは以下のように編集する。

app/view/home/top.html.erb
<hello-temp></hello-temp>

そして、app/view/layouts/application.html.erbにはhello_temp.js読み込むために以下のように編集する。

app/view/layouts/application.html.erb
  <!--省略-->
 <body>
    <%= yield %>
    <%= javascript_pack_tag 'hello_temp' %><!--追記する-->
  </body>
 <!--省略-->

①(1-A):hello_temp.jsのみを使ってVue.componentnew Vue()の両方を書く方法

app/javascript/packs/hello_temp.js
import Vue from 'vue'

  Vue.component('hello-temp', {
  template: '<p>hello template!</p>'
  })

  new Vue({ el: 'hello-temp' })

MEMO:vueの公式ドキュメントにはまず一番最初にこの方法が書かれている。

②(1-B):hello_temp.jsのみを使ってnew Vue()のみで書く方法

app/javascript/packs/hello_temp.js
import Vue from 'vue'

  new Vue({
    el: 'hello-temp',
    template: '<p>hello template!</p>'
 })

MEMO:1-Aよりかは単純にかけて良い気がする。

③(2-B):hello_temp.jshello_temp.vueを使ってVue.componentnew Vue()の両方を書く方法(単一ファイルコンポーネント化)

テンプレートをhello_temp.vueとして別ファイルで作成し、それをhello_temp.jsでインポートしてコンポーネント化する。

app/javascript/packs/hello_temp.js
import Vue from 'vue'
import HelloTemp from '../components/hello_temp.vue'

  new Vue({
    el: 'hello-temp',
    components: { HelloTemp }
 })
app/javascript/componets/hello_temp.vue
<template>
  <p>hello template!</p>
</template>

MEMO:jsファイルを全てのテンプレート(vueファイル)ごとに作ると、ファイルが増えすぎるので、app.jsとかにまとめてインポートして、コンポーネント化する方がいいかもしれない。

④(3-B,C):hello_temp.vueapp.jsapp.vueを使って、new Vue()export default {components: {}}で書く方法

この方法は、hello_temp.vueで作成したテンプレート(今回はhello_vue.vueも)をapp.vueでインポート・コンポーネント化して、それをテンプレート化して、それを更にapp.js<app></app>としてコンポーネント化する。(ややこしくてすいません)
このケースでは他のケースと異なり、top.html.erbapplication.html.erbも以下の通り変更する。

app/view/home/top.html.erb
<app></app>
app/view/layouts/application.html.erb
  <!--省略-->
 <body>
    <%= yield %>
    <%= javascript_pack_tag 'app' %><!--追記する-->
  </body>
 <!--省略-->

この方法では、比較のためにテンプレート上記のhello_temp.vueのほかにhello_vue.vueも作成する。

app/javascript/componets/hello_temp.vue
<template>
  <p>hello template!</p>
</template>
app/javascript/componets/hello_js.vue
<template>
  <p>hello vue!</p>
</template>

そして、この2つのテンプレートをapp.vueでインポートしてコンポーネント化して、それをさらにテンプレート化する。

app/javascript/componets/app.vue
<template>
  <div>
    <hello-temp></hello-temp>
    <hello-vue></hello-vue>
  </div>
</template>

<script>
import HelloTemp from '../components/hello_temp.vue'
import HelloVue from '../components/hello_vue.vue'

export default {
  components: {
    HelloTemp,
    HelloVue
  }
}
</script>

MEMO:importしたときにパスカルケース(HelloTemp)のように宣言すれば、<HelloTemp></HelloTemp>でも<hello-temp></hello-temp>でもコンポーネントを呼び出せる。

そして、これをapp.jsでコンポーネント化する。

app/javascript/packs/app.js
import Vue from 'vue'
import App from '../components/app.vue'

  new Vue({
    el: 'app',
    components: { App }
 })

すると、

app/view/home/top.html.erb
<app></app>

にコンポーネントが代入される。

MEMO:このやり方だと、htmlは基本的にいじらずに、テンプレートのapp.vuehello_temp.vue、読み込むためのapp.jsを編集していく形になる?

その他

テンプレートに複数の要素を入れる場合、<div></div>で囲う必要がある。
例えば、

sample.vue
<template>
  <p>first line</p>
  <p>second line</p>
</template>

といった書き方は出来ない。

まとめ

qiitaにまとめることによって、vue.jsのコンポーネント化についてかなり理解が深まった。
コンポーネント化は、いくらでも書き方はある(この記事にまとめた方法以外にも)が、個人的には最後の④(3-B,C)のやり方がファイルも増えすぎず、管理がしやすい為、一番良いと思った。

コンポーネント化がしっかり使いこなせたら、すこしはカッコイイエンジニアになれる気がする(笑)。

おまけ(railsでvue.jsを使うときにつまずいたこと。)

単一ファイルコンポーネントでコンパイルエラー発生

railsで単一ファイルコンポーネントを使おうとしたら、コンパイルのエラーが出てしまった。
このQiitaを参考にwebpackのconfigを書き替えたら解決しました。

config/webpack/production.js(development.jsなども同様)
const environment = require('./environment')

module.exports = Object.assign({}, environment.toWebpackConfig(), {
  resolve: {
    alias: {
      'vue$': 'vue/dist/vue.esm.js'
    }
  }
})

感謝!!!

また、私の使用したのwebpackerでは、webpack-dev-serverを勝手に入れてくれるので、コンパイルも更新時に勝手にやってくれました。

単一ファイルコンポーネントでcssが適用されない

単一ファイルコンポーネントではvueファイルにjavascriptやcss(スタイル)を書くことが出来る。
こんな感じ。

app/javascript/componets/hello_js.vue
<template>
  <p>hello vue!</p>
</template>

<style scoped>
p{
font-size:20px;
color:red;
}
<style>

<script>
</script>

scopedをつければ、他のコンポーネントに影響せずにスタイルを管理できる。
これが、コンポーネント化の大きなメリットである。

しかし、railsの場合、なぜかstyleが適用されない。

このサイトの通りに以下のように追記するとうまく適用されるようになった。

app/view/layouts/application.html.erb
 <!--省略-->
 <body>
    <%= yield %>
    <%= javascript_pack_tag 'hello_temp' %>
    <%= stylesheet_pack_tag 'hello_temp' %><!--追記する-->
  </body>
 <!--省略-->

以上