RailsでVue.jsを使うときは、なるべくRailsアプリケーションとVueアプリケーションを独立させて、データはAjax経由だけでやり取りしたい。
とは言っても、Railsのデータ(paramsやインスタンス変数)を直接Vueに渡したい、というケースに出くわすことがあります。そうした場合のノウハウが見当たらないので、まとめてみました。
環境
Rails 6.0、Webpacker 4.0、Vue.js 2.6
1. URLのクエリーから取る
URLに/entries?foo=bar
のようなクエリー部があれば、JavaScriptだけで取り出せます。ここでは、qsライブラリを使います。/entries?entry[title]=bar
のようなクエリーも処理できます。
$ yarn add qs
import qs from 'qs';
// 中略
let params = qs.parse(location.search.slice(1));
console.log(params.foo); // bar
この方法では、/users/123/entries
からuser_idを取り出す、といった場合が面倒になります。パスのパターンをいちいちJavaScript側に書かないといけません。
2. data属性とpropsを使う
追記:いろいろ勘違いしていたことがあったので、2.は修正しました(2019/11/10)。
ここからヒントを得ました:Passing Props to Vue in a Rails View - Devan Flaherty - Medium 。こちらも参照してください: new Vue(App) と new Vue({ render: h => h(App) }) の違い。
Railsのビューではdata属性で渡したいデータを指定します。
<%= content_tag :div, '', id: "entry-list", data: { user_id: @user.try(:id) } %>
Vueインスタンスを作る際には、renderを使い、propsにdata属性の情報を渡します(jQueryを使っています)。
import EntryList from '../entry_list.vue';
document.addEventListener('DOMContentLoaded', () => {
new Vue({
el: '#entry-list',
render: h => h(EntryList, { props: $('#entry-list').data() })
});
VueアプリのObjectにpropsを加えておけば、propsの名前を使って取り出せます。
<script>
export default {
props: ['userId'],
// 中略
created() {
let userId = this.userId;
}
)
</script>
Vueアプリの独立性を気にしないなら、createdかbeforeMountで直接data属性の値を取ることもできます(mounted以降のサイクルではできません)。
<script>
export default {
// 中略
created() {
let userId = $('#entry-list').data('userId');
}
}
</script>
3. グローバル変数を使う
グローバル変数を使ったっていいじゃないか。
Railsでこうやって
def shared_data
@shared_data ||= {}
end
helper_method :shared_data
こうやって
<script>
var sharedData = <%= shared_data.to_json.html_safe %>;
</script>
こうやっておいてから (追記:Turbolinksだと実行されないことがあるので、headではなくbody内に入れる)
<%= render 'shared/shared_data' %>
こうやって
def index
@user = User.active.find(params[:user_id]) if params[:user_id].present?
shared_data[:user_id] = @user.try(:id)
end
Vueでこうします。
<script>
export default {
// 中略
created() {
let userId = sharedData.user_id;
}
}
</script>
Vueアプリケーションの独立性が落ちるのが気になるなら、グローバル変数を先ほどのpropsに渡す、という手もあります。
とここまで書いてから、グローバル変数をpropsに渡すのがベストな気がしてきました。