東京の皆さんこんにちは。ご機嫌いかがでしょうか? 私は今、旅行に来た新潟のホテルでこの記事を書いています。うまい酒にうまいメシ。なぜ我々は東京に固執するのでしょうか?私は今最高に幸せ( 忘れてた 時間がなくて終わっていないアドベントカレンダーの原稿書きがなければもっと最高だったのに)です。美味しい日本酒のおかげで捗るこの記事は、きっと 12/2 に公開( 追記: ダメでした )されるでしょう。
さて、表題の「私は jQuery から Vue.js への置き換えで何をやらかしたのか」について語って行きましょう。ちょうどアルコールも入っているし、色々語れそうです。
忙しい人向けまとめ
Q. jQuery でがっつり UI 組まれた Rails アプリで脱 jQuery を図るなら、気をつけないといけないところはどこでしょう?
A. 本当に jQuery が問題なのかよく考えましょう
jQuery なコードをいきなりモダンな FW にするよりかは、 jQuery なコードをその FW 風に書き換えていってから置き換えたほうが、多分楽です。あなたのコードの辛いところは、「状態(モデル)」「表示 (ビュー)」「イベントハンドリング (コントロール)」が全部一緒になってるから辛いんであって、jQuery が辛いわけではないだろうから……
背景: jQuery だけで大規模なのやるのつらすぎワロタ
さて、時は 20xx 年、Webpack が流行る前で Browserify が流行っていた頃のお話。私の目の前には元気に動く jQuery がいました。 (jQuery を使っていることはもちろん公開情報です。じゃないと MIT ライセンス違反になっちゃいますし、実際 cw-assets.crowdworks.jp/appplication_pc-${HASH}.js を読むと、ちゃんと jQuery のライセンスが読めるはずです)
当時の Rails では 控えめなJavaScript が推奨されていましたし、恐らくは最初の CW 開発者たちもそれに従って js を組んでいたのだと思います。それで全く問題はありませんでしたが、「ページ数」が 3 桁を超える頃からだんだんきつくなったのでしょう。ページごとに JavaScript をかけるような工夫を行いました。
具体的な方法については非公開ですが、 <body>
タグに振られた id
属性と data-*
属性を見れば何をしたか察していただけるでしょう。
さて、ページごとにスクリプトをかけるようにはなりましたが、 jQuery 特有の、問題もあります。
jQuery は名前の通り、クエリっぽく DOM 操作ができる便利ライブラリですが、どのように「状態」を保持するかについては特に何も教えてくれません。なので、「状態」によって分岐をするコードは往々にして荒れてしまいます。例えば以下のコードです。
var isShownFoo = $('.foo').is(':visible');
$('.bar').on('click', function( e ) {
if ( !isShownFoo ) {
$('.foo').show();
}
});
このコードは最初は全く問題がないのですが、ある日、気がつくと変数のスコープや、変数の初期化コードがどこにいったのかわからなくなりがちです。
そして、「クエリ」の重ね合わせコードが気軽にかけてしまうので、往々にしてネストが深くなりがちと言う問題もあります。
$('.foo').click( function( e ) {
$.ajax(opts)
.done(function(data) {
// do something
});
});
たいていの場合はこの辺りが組み合わさった複雑さが生まれます。ふと気付いた時には
- 状態 A は
$('#foo-component').ready(function() { /* ここ */ });
のスコープ内でのみ参照できる変数a
から参照すること - 状態 B は
$('#bar-component').data('attr-b')
から参照すること - 状態 C はグローバルスコープの
MyObj.$attr_c
から参照すること
みたいなルールが組み合わさって存在している状態などになります。
もちろん、これだけではなく、そもそもデータ構造が複雑なのでフォームのデータや API のエンドポイントで受け取る JSON の構造自体が複雑である問題や、サーバサイドでもバリデーションやその他諸々が複雑だという問題ももちろんあります。
あるエンティティに「下書きフラグ」をつけ、結果としてエンティティの状態管理が異常に大変になったことがある人のみが石を投げなさい
jQuery の特性に基づく難しさと、大規模特有の難しさ、その他諸々が組み合わさった「闇」と言われるコード群が、そこにはあったのです。(長年「闇」と言われたコードがあるのは 公開情報 です)
この頃の私たちは、jQuery でできたフロントエンド層を「テストがしづらいコード」と呼んでいました。
XXX (嫌いな言語, FW, ツールをお入れください) はクソだ! ZZZ (好きな言語, FW, ツールをお入れください) こそ至高!
さて、時は 20xx 年、Webpack が流行る前で Browserify が流行っていた頃、とにもかくにも jQuery に対するヘイトが某社の社内にはくすぶっていました。
キラキラしたフロントエンド界隈に比べて、俺たちはなんてざまだ! 21 世紀もなってまだ jQuery を使っているなんて……世間では Angular や React が流行っているぞ! 調べてみたら Vue, Riot, Ember とか新しいやつも出始めている…… もう foo.js.coffee.erb
とか嫌や……
私たちは検討を重ねた上、 Vue.js こそが銀の弾丸であり、Browserify を使うことで ES2016 を使えば全てが救われ、ナウでヤングなプロダクトになるはずだと結論づけました。そんなわけでプロジェクトチームが立ち上がったわけです。ミッションは「 jQuery のつらみをなくすこと 」、ゴールは仕事を依頼できそうな某画面を Vue.js にすることでした。
お察しのいい読者の皆様なら既にお気づきかと思いますが、私たちは既に間違いを犯していました。
そもそも辛いコードというのは大抵の場合、 使い方が悪いのであって、道具が悪いわけでは無い のです。
foo.js.coffee.erb
が誕生したということは、そもそもフロントエンド層が必要とするデータを取得するための API エンドポイントを用意していなかったからでしかありません。
今回のターゲットであった仕事を依頼できそうな某画面では、
- そもそもデータ構造が過度に複雑である
- 様々な状態に応じて表示内容が変わりすぎる。少なくとも3桁パターンはあるはずだ。
- 上記2つの結果 として、 jQuery のコードが複雑になっている
という構造になっていましたが、私たちはそんなものを見なかったことにしました。
(そもそも PO がいなかったからこんなことになったんだ)
そして、 Vue.js を入れればテストができると言う信仰を信じて、 jQuery スパゲティの何が問題なのかを深堀することはなかったのです。
jQuery から Vue.js に置き換える時の一般的な話
本題の jQuery から Vue.js に置き換えるに当たって何をやらかしたのか、一般論をベースに話していきましょう。
まず、Sprockets へのヘイトもそこそこ溜まっているチームで、 es2016 を使いたいチームの場合なら、大抵の場合は Rails アプリのディレクトリに package.json
を作ることになります。そして、 Vue.js やその他必要なツールなどをインストールします。この辺の構成については拙記事 ( はじめてのVue.js - 単一ファイルコンポーネントを作れる環境構築編 (npm + gulp + browserify + babel + vueify) ) を参照していただけると、うっすらと想像ができるかと思います。
この構成では一つのプロジェクトディレクトリ中に Rails と node のコードが混じることとなりますが、この辺は好き好きでしょう。個人的には特段問題があるとは思いません。(問題がないわけではなない。例えば、 /config
はあくまでも rails の設定であり、 js の設定置き場について考慮が必要である)
サーバサイドの Rails とフロントエンドの Vue.js との間でデータをどのようにやり取りするかについては考え方がいくつもあり得ますが、最初は data-
アトリビュート、次に JavaScript へのダイレクトな埋め込み、 APIエンドポイントを作り次第 JavaScript 側でのモデル管理実装をする流れがベターだと思います。特に、最後のモデル管理実装は今なら Vuex が使えるのではないかと思います。この辺で私がどのようなやらかしをしたのかを言うことはできませんが、少なくともやらかしたのは確かです。マジごめん。
とりあえず、今やり直すならモデル/データ管理に vuex を使いたい。僕はそう思います。
この話が行われていた当時に一切試さなかった話ですが、可能ならいきなりフロントエンドのライブラリを導入するよりも jQuery のみで MVVM パターンへ移行したほうがよかったかなぁと今になると思います。 結局のところ、jQuery で苦しんでいたのは、複雑な「状態」が表示やイベントハンドル系のコードとごっちゃになっていたから です。
つまり、本来やるべきだったのは、「状態」「表示変更」「イベントハンドリング」のコードの分離 だったのでした。
Vue.js なんていらなかったんや・・・
ああ、メタに! メタに!
ここにいろんな組織の問題を書いたんだけど、よく考えたら公開しちゃダメだから消した。ただ、今年のクラウドワークスアドベントカレンダーの記事 を読むと、そうだなと言う気分でいっぱいになる。もしこの記事を読んでいるあなたがマネージャだったとしたら、人を成果で判断するのかプロセスで判断するのかは、もっと明確にチームに伝達したほうが良いのかなぁと思う。