RailsにVue.jsを小さく導入する
先月開催されたフロントエンドもくもく会 - 新年LT大会というイベントで 「RailsにVue.jsを小さく導入する」というLTを発表したが、あえなく時間切れになってしまった。
LTはTurbolinksはどんなもので、敬遠されがちという説明で終わってしまったので、 続きをブログに書くことにした。
前提
どんなRailsアプリケーションにVue.jsを導入したかというと
- Rails 5.0で作られ、現在Rails 5.1を使っている
- Turbolinksを使っている
- jQueryに依存している
- APIやjbuilderでJSONを返してはくれない
RailsをAPIに専念させ、フロントエンドをSPAで実装するには時既に遅しである。
Vue.jsのインストール
まずはWebpackerでVue.jsを導入することにする。導入方法はwebpackerのREADMEを参照すればよいので割愛する。
webpackのエントリーポイントはapp/javascript/packs/main.js
とした。
レールに乗っかれるのはここまで。あとはご自由にという感じである。
基本
Vue.jsはコンパイラ付きの完全版を使い、Turbolinksと共存させたいので、vue-turbolinksを使う。turbolinks:load
イベントでVueコンポーネントをマウントする。コンポーネントのマウント先はViewテンプレートに記述したIDになる。
/ RailsのViewテンプレート.slim
#mount-point
/* app/javascript/packs/main.js */
import Vue from 'vue/dist/vue.esm';
import ComponentA from 'components/ComponentA.vue'
import TurbolinksAdapter from 'vue-turbolinks';
Vue.use(TurbolinksAdapter);
document.addEventListener('turbolinks:load', () => {
if (document.getElementById('mount-point')) {
new Vue({
name: 'SampleComponent',
el: '#mount-point',
template: '<ComponentA/>'
components: {
ComponentA
},
});
}
});
div#mount-point
にComponentA
がマウントされる。これで任意のViewテンプレート内にVue.jsを導入できる。小さく導入できた。
応用
コンパイラを含むVue.jsの完全版を利用しているので、DOMテンプレートを利用できる。 以下のようにRailsのコントローラーから渡された変数をVue.jsのコンポーネントに受け渡すことができる。
/ RailsのViewテンプレート.slim
#mount-point
component-a([email protected])
component-b
/* app/javascript/packs/main.js */
import Vue from 'vue/dist/vue.esm';
import ComponentA from 'components/ComponentA.vue'
import ComponentB from 'components/ComponentB.vue'
import TurbolinksAdapter from 'vue-turbolinks';
Vue.use(TurbolinksAdapter);
document.addEventListener('turbolinks:load', () => {
if (document.getElementById('mount-point')) {
new Vue({
name: 'SampleComponent',
el: '#mount-point',
components: {
ComponentA,
ComponentB
},
});
}
});
RailsということでActionView::FormHelperも使える。こんなこともできるが、これは横着である。
/ RailsのViewテンプレート.slim
= form_with model: Item.new do |f|
.form-group#mount-point
= f.text_field :name,
class: 'form-control',
'v-model.trim' 'nameValue',
'v-on:blur': 'checkValue'
/* app/javascript/packs/main.js */
import Vue from 'vue/dist/vue.esm';
import TurbolinksAdapter from 'vue-turbolinks';
Vue.use(TurbolinksAdapter);
document.addEventListener('turbolinks:load', () => {
if (document.getElementById('mount-point')) {
new Vue({
name: 'SampleComponent',
el: '#mount-point',
data: function() {
return {
nameValue: ''
}
},
methods: {
checkValue: function() {
if (this.nameValue.length < 3) {
// 何かしらの処理
}
}
}
});
}
});
さらにbody
タグにマウントポイントを設定すればよいのでは?と試したら動作するが、既存のjQueryプラグインやChartkick(Ruby版)が動作しなくなってしまったので断念した。順次Vueに移行していけば解決する見通しはある。
注意点
上記の方法を使えばページ内にVueコンポーネントを展開できる。例では一つだが、必要に応じて何個もVueコンポーネントを展開できる。
ただし、それぞれのコンポーネント間で状態を共有したい場合はVuexを使う必要がある。
また、vue-turbolinksはソースコードが短いので一読をすすめるが、その動作はページを移動するごとにVueコンポーネントは破棄している。よって、ページをまたいで状態を維持したい場合、自分はvue-persistedstateで状態を永続化と復元をしている。
やはりTurbolinksなのでSPAのような動きをしているが、違うのである。
その他にVue.jsはDOMをテンプレートとして利用できるが、特有の注意事項を理解した上で利用するとよい。
まとめ
- Turbolinksは共存できる。
- RailsのViewとVueが混在していてカオスであるが、受け入れればなんとかなる。
- これを5分のLTで説明するのは無理だ。