私たちはなぜReactではなくVue.jsを選んだのか

私たちはなぜReactではなくVue.jsを選んだのか

Qwintryチームは最近、既存のすべてのプロジェクトのフロントエンドをVue.jsに移行しはじめました。新しいプロジェクトでもVue.jsを使います。

  • レガシーなDrupalのシステム(qwintry.com)
  • ゼロから新しく書きなおすqwintry.comのブランチ
  • Yii2で動くb2bシステム(logistics.qwintry.com)
  • その他、比較的小さめのプロジェクト(ほとんどは、PHPとNode.jsでバックエンドを構築しているもの)

プロジェクトの規模についていうと、Qwintryは世界中で約50万人の顧客が使っています。アメリカとドイツに倉庫を持っていて、アメリカ国内最大の郵送先のひとつで、東欧や中東への出荷に注力しています。Qwintryは、アメリカのオンラインストアでグッズを購入する人たちのためのツールです。私たちの倉庫に届いた荷物をコントロールパネルで管理できて、複数の荷物を取りまとめたり写真で確認したりできます。そして、私たちの倉庫から世界中に配送することで、配送コストを劇的に下げているのです。自前のITシステムとロジスティックチェーンを活用して、最高品質の配送サービスを格安で提供しています。

Qwintyから届いた箱

お客様のもとに届いた小包。ユーザーレビューより

既存の大量のコードベースは、大半がPHPとJSで書かれていました。

いまどきのフレームワークをいくつか評価した結果、私たちはVue.jsを使うことに決めました。評価にあたっては、ReactとVue.jsとAngular2のそれぞれを使って料金計算機を実装してみました。

React.jsについて思うこと

ReactはJS界での括弧たる地位を固めました。フロントエンドのビューフレームワークを検討するにあたって、今やJS開発者のデフォルトの選択肢になっているといってもいいでしょう。

私はReactで、いくつかのSPAと動的ウィジェットを作ってみました。また、iOSでのReact NativeやReduxについても試してみました。ステートを考慮するようになったという意味では、ReactはJSの世界を大きく前進させたと思っています。また、真の関数プログラミングについて実用的な方法を示してもくれました。React Nativeは偉大です。ネイティブアプリ開発を大きく変えてしまいました。

しかし、Reactにはこんな弱点もあると思います。

純粋性や不変性などのイデオロギーを「とりあえず完成させる」ことよりも重視する

誤解しないで欲しいのですが、純粋関数とシンプルなrender()によるアプローチはいいものだと思っています。現実的でうまく機能するすばらしいアイデアであることは間違いありません。ここで私が言いたいのは違うことです。

こんなレベルでの厳密さや純粋性は、1000人の開発者をかかえるチームでなら有用でしょう。そう。ちょうど、すべてのPHPコードに厳格な型指定を持ち込むために独自構文を作ろうと決めるようなものです。あるいは、これまでHaskellを書いていた開発者がJSの世界にやってきたときにも便利です。でも、そんな大規模な開発チームを持つ企業はそうそうないし、Facebookの目指す道とは違うゴールに向かう企業も少なくありません。この件については後ほど詳しく説明しましょう。

忌まわしきJSX

はいはい、わかります。わかってますって!JSXは「プレーンなjavascriptに特別な構文を追加しただけのもの」だって言いたいんでしょう?フォームの見栄えをよくしようとしたデザイナーは、大量のdivで要素を囲みます。純粋でプレーンなES6などではないのです。Reactコンポーネントに何らかのデザインを適用するのもたいへん。JSXは可読性に欠けるからです。HTMLコードブロックにごくシンプルなIF条件をつけることすらできません。Reactファンの人たちは「三項演算子があればそんなものはいらないでしょう」と言うでしょうが、信じてはいけません。実例を見てみましょう。最終的にピュアJSにコンパイルされるとはいえ、みなさんが編集したり読んだりするのはHTMLとJSがこんなふうに入り混じったものです。

<ul>
       {items.map(item =>
         <li key={item.id}>{item.name}</li>
       )}
</ul>


多くの開発者は、文法上のこの制約が自分たちに力を与えてくれるコードのモジュール化を推進するのに役立つと考えています(私もかつてはそうでしたが、今となってはそうは思いません)。コードを小さめのヘルパー関数にまとめて、それをrender()関数の中で使うようにせざるを得なくなるからです。まさにこの人が提案しているようなことです。

JSXを使うと、15行のHTMLからなるコンポーネントをさらに三つに分割せざるを得なくなることもあります。

そんなコードを構造化しなくてはいけなくなることを、開発者として成長するためのチャンスだなどと思わないでください。

実際は、こういうことです。

比較的複雑なコンポーネント(githubで公開してhackernewsで披露するようなものではないでしょうね)を書くときに、ただJSXの制約のためだけに小さく分割するというのは、実際のビジネスのタスクをこなすうえでは本質から外れた作業です。いや、小さなコンポーネントにするというアイデア自体がだめだとかうまくいかないとか言っているわけではないのです。

コードをコンポーネントに分割することで、管理しやすくなるし再利用性も高まる。そのことはきちんと認識しておくべきです。でも、そうすべきなのは、コード中のそのロジックが自前のpropを持つコンポーネントとして使えそうな場合だけです。三項演算子を使うためにすべてのIF文の中身をコンポーネント化すべきだというわけではありません!あちこちに新しいコンポーネントを作るたびに、あなたのフロー状態は損なわれるでしょう。ビジネスのタスクを考える頭(今扱っているコンポーネントのステートモデルを把握していて、あとはhtmlを多少足して動くようにすればいい状態)から「マネージャー的な考え方」(新しいコンポーネント用にファイルを作って、新しいコンポーネントのpropを考えたりそれをステートにマップする方法を考えたりコールバックを渡す方法を考えたり……)に切り替える必要があるからです。

結果としてコードを書くスピードは落ちます。本来必要としない場面での早すぎる過剰なモジュール化を強いられるからです。早すぎるモジュール化は、早すぎる最適化と同じようなものではないでしょうか。

私やチームのメンバーにとってコードの可読性は重要ですが、楽しくコーディングできることのほうがそれよりもずっと大事です。ごく単純な計算機ウィジェットを作るために6個のコンポーネントを作るなどという作業はつらいものです。そんなコンポーネントは保守性が悪くなるものです。変更を加えたり見た目を調整したりするのも難しいでしょう。あちこちのファイルに散らばった関数やHTMLを行き来する必要があるからです。念のために繰り返しますが、決してモノリシックなコードを推奨しているのではありません。日々の開発には、マイクロコンポーネントではなくふつうのコンポーネントを使いましょうということです。普通はそうですよね。

ReactでフォームとReduxを扱っていると、一日中タイピングしなければいけなくなる

Reactが重視するのは純粋性、そしてクリーンな一方通行のフローでしたよね?だからこそLinkedStateMixinがやっかいもの扱いされているのだし、10個の入力を得るためには10個の関数を作る必要があるのです。それらの関数の80%はthis.setState()を呼ぶたった1行だけだったり、Reduxのアクションを呼び出すだけだったりします(そしておそらく、その入力ごとに新たに10個の定数を作る必要が出てくるでしょう)。こういったコードを自動生成できるのならまだ救いはありますが、このあたりを劇的に改善してくれるIDEをまだ見つけられていません。

いったいなぜこんなにタイプ量が増えるのでしょう?大規模業務アプリを作るような人たちに言わせると、双方向バインディングは危険なものだということだからです。双方向のデータフローを持つコードはすっきりしておらず読みづらいというのは認めましょう。しかしそれは、双方向バインディングだけの問題じゃなくてAngular 1そのものの問題でもあるのではないでしょうか。そして、それが最大の問題であるとはいえないと思います。

最近Vue.jsで作ったquick-editorコンポーネント群をごらんください。これはDrupalで作ったWebサイトで使うために作ったものです(デザインについて補足しておきましょう。これは社内のオペレーター用のバックエンドのUIです。デザイナーたちは顧客向けのフロントエンドインターフェイスを作るのに忙しくて、こっちのほうまで手が回っていないのです)。


諸事情によりコードを見せることはできませんが、これをVueで書くのはとても楽しかったし、とても読みやすいコードになりました。

Reactでこの手のウィジェットを作ろうと思えば、入力ひとつひとつに対して個別の関数を作らなければいけなかったでしょう。きっとつらかったに違いありません。

Reduxもまた、冗長性そのものに思えます。MobxがReactからAngularに移ったのは双方向バインディングを使いたいからだろうという開発者はあちこちにいます。頭のいい人たちは、仕事をやりとげることよりもコードベースの純粋性を保つことを重視しているかのようです(納期というものが存在しないのであればそれでもいいでしょうけどね)。

必要な道具が多すぎる

ReactはBabelを念頭に置いて作られました。大量のnpmパッケージ、そしてES5に変換するコンパイラがなければ、Reactアプリを動かせません。React公式の導入パッケージのコードを使ったシンプルなアプリでも、node_modules内のJSのコードは約75MBにもなってしまいます。

致命的な問題ではないし、Reactの問題というよりはJS全体に関する問題ではあります。それでも、Reactを使うときには不満を感じてしまいます。

Angular 1:自由すぎるのも考えもの

Angular 1はすばらしいフロントエンドフレームワークです。とっつきやすさや可読性の面において、JS界でReactの対極に位置するものでしょう。お手軽に使い始められるし、最初の千行くらいを書くのはほんとうに楽しいことでしょう。ただそれ以降は、現実的にはつまらないコードを書くことを強いられます。おそらく、ディレクティブやスコープに関して混乱してしまうでしょう。アプリケーションのあらゆるレイヤー間での双方向のデータフローは、新入りの開発者には手が出せないものになるかもしれません。とても管理しきれないからです。

どうしてこうなったのでしょう?

Angular.jsが作られた2009年当時のフロントエンドは今よりもずっとシンプルで、ステートのことなど考える人は誰もいなかったのです。当時の開発者たちを責めることはできません。そのころ作ろうとしていたのはBackboneに対抗しうる何かであって、できるだけタイプ量を減らそうとしていたのです。

Angular2

単純なHello Worldアプリを作って、どれだけの数のファイルができあがるかを見てみましょう。Typescriptとコンパイラを使わないと動かすことができません(Typescriptについては、日々の作業を楽しめるものかどうかを確かめられていません。この件に関しては、Eric Eliottがうまくまとめています)。私にとってはそれだけでもうだめでした。さらに、実際にやりたい作業を始めるまえに大量のタイピングが必要になります。どうもAngular 2の人たちは、平均的なユーザーがタスクをこなすためのフレームワークではなく、Reactに対抗しうる完璧なフレームワークを作ろうとしているようです。私の認識が間違っているのかもしれないし、今後考えが変わるかもしれません。Angular2での開発経験はそんなに多くなくて、評価用に料金計算機のデモを作ったくらいだからです。Vue.jsのサイトにすばらしい比較記事があります。このページによると、Angular2はすばらしいフレームワークで、思想的にVueと共通するところも多いそうです。

Vue.js

ひとことで言ってしまえば、Vue.jsはまさに私が長年求め続けてきたものです(ここで言っているのはVue.js 2のことです。最初のバージョンのVueに比べて大きく改良された、現時点の安定版にあたります)。エレガントさと簡潔さ、そしてとにかく何かを完成させることに注力しているという点において、JSを大きく変えたといえるでしょう。2007年にjQueryに接したとき以来の衝撃でした。

この人気度グラフを見れば、そう感じているのが私だけではないことがわかることでしょう。

Vue.jsは2016年に最も急成長したJSフレームワークのひとつです。3か月ごとに最新フレームワークに乗り換えるような人たちが大げさに騒いでるだけではないし、どこかの大企業が権力(とお金)にものを言わせて押し付けているわけでもなさそうです。

LaravelはVue.jsをコアに取り込みました。これは大きいことですね。

Vue.jsの利点

Vue.jsは、可読性や保守性と楽しさとのバランスが絶妙です。ReactとAngular 1のちょうど中間に位置しているものです。Vueのガイドラインを見れば、これらのフレームワークからいろいろ取り入れている部分をすぐに見つけられることでしょう。Reactから取り入れたのは、コンポーネントベースのアプローチやprop、コンポーネントの階層間での単方向のデータフロー、仮想レンダリング、そしてアプリケーションのステート管理の重要性です。一方Angularからは、使いやすい構文のテンプレートを取り入れています。また、単一のコンポーネント内では必要に応じて双方向バインディングもできるようになっています。

Vue.jsはとてもとっつきやすいものです。自分たちのチームでそれを実感しました。デフォルトではコンパイラを使わないので、レガシーなコードベースにVueを追加するだけで、jQueryでごちゃごちゃしたコードをよりよいjSに改善できるようになります。

適度なマジック

Vue.jsはHTMLに関してもJSに関しても非常に扱いやすいものです。ビジネスタスクから気をそらせることなしに複雑なテンプレートを扱えるし、サイズが大きくなってもテンプレートの可読性は落ちません。ビジネスタスクを完成させたら、テンプレートを小さめのコンポーネントに分割したくなるかもしれません。そうすれば、開発開始当初よりもアプリの全体像がよりよく把握できるようになるでしょう。

経験的に、このアプローチはReactのときとは大きく異なります。Vue.jsにすることで、時間を大きく節約できるようになりました。Reactの場合は、コードを書き始めたその時点で、有無を言わさずマイクロコンポーネントへの分割を強いられました。文字どおり、混乱したコードに埋もれてしまう感じでした。Reactでは、再利用することなどまずないようなマイクロコンポーネントのpropを何度となくリファクタリングしなければ、今書いているコード内のロジックのフローを変更すべきかどうかを判断できませんでした。

htmlフォームを扱うのもVueなら簡単です。双方向バインディングがここで活きてきます。複雑なケースでも私には特に問題はありませんが、watcherを見るとAngular 1を思い出すかもしれません。コンポーネントを分割することになったなら、いつだってコールバック渡しによる単方向のフローも使えます。

コンパイラやlintやPostCSSやES6といったものが欲しければ、使えます。Vueエクステンションは、Vue.js 2で書いたコンポーネントを公開するときのデフォルトの方法になりつつあります。コンポーネントスコープのCSSというアイデアはすばらしいもので、CSSの階層に適切な名前をつけたりBEMのようなテクノロジーに頼ったりする手間を省けるでしょう。

Vue.jsのコアには、きわめてシンプルなステート管理やprop管理の仕組みが用意されています。それぞれdata()メソッドとprops()メソッドを使うものであり、実際何の問題もなく動きます。もう少しきちんと関心の分離を行いたいのであればVuexが使えるでしょう(私の理解が正しければ、これはReactにおけるMobxに、ちょっとした状態遷移を加えたものです)。

Vue.jsのユースケースの多くはVuexのようなステート管理を必要としないものだと思いますが、必要に応じて使える選択肢があるのはよいことです。

Vue.jsの弱点

  1. 最大の弱点は、テンプレートの実行時エラーがわかりくいところです。この点はAngular 1とまったく同じです。Vue.jsは有用な警告をたくさん出してくれます。たとえばpropを変更しようとしたり、data()メソッドの使いかたを間違えているときなどに警告が発生します。このあたりは、Reactの長所をうまく取り入れていますね。でも、テンプレートの実行時エラーについては、Vueには難があります。何度となくあらわれる例外スタックトレースはあまり便利ではないうえに、Vue.jsの内部的なメソッドにまで立ち入ってしまいます。
  2. フレームワークが未成熟であること。コミュニティが提供するコンポーネントはどれも不安定です。その多くはVue.js 1向けに作られたものだし、githubのリポジトリを見てもどのバージョン向けのコンポーネントか判断しづらいのです。この問題は、Vueではライブラリを追加しなくてもたいていのことができてしまうという事実によって相殺されます。必要になるとすれば、何らかのajaxライブラリ(isomorphicなアプリやaxiosなどのことを気にしないのであれば、vue-resourceをお勧めします)や、vue-routerくらいでしょう。vue-routerはコアライブラリだとみなされており、十分なサポートがあります。
  3. コミュニティが提供するライブラリのあちこちに散らばる中国語のコメント。そんなに驚くことではありません。Vue.jsの作者は中国人であり、Vue.jsは中国でとても人気があるのです。
  4. ひとりプロジェクトであること。ほんとうに問題であるかどうかはともかく、それを認識しておく必要があるでしょう。Vueの作者であるEvan Youは、GoogleやMeteorで働いていたことのある開発者です。Laravelもずっとひとりプロジェクトとして続いています。今のところは大成功を収めているけれど、今後どうなるかは……。

Drupalで使うVue.js

おことわり:今のところQwintryでDrupal 8を使う予定はありません。より高速かつシンプルなPHPとNode.jsのフレームワークへの移行中であり、レガシーなコードベースはDrupal 7向けのものです。

現行のqwintry.comはDrupalで動いているので、新しいフレームワークの検証もこの環境で行うことが重要でした。現行システムのコードは自慢できるようなものではありませんが、きちんと動いて収益を上げていました。そこでそれを尊重し、改良しつつさまざまな新機能を作っていきます。これまでにVueとDrupalで作ったものを紹介しましょう。

まずは、複雑な注文エンティティ用のその場でのノード編集機能。顧客向けの請求書の発行や、個別の商品のクイック編集機能も含みます。ノードの読み書き用に基本的なJSON APIを作る必要がありました。凝ったことをしているわけではなく、単にメニューのコールバックとして使うだけです。

私たちが使っているプロプライエタリなSaaSソフトウェア用の、RESTベースの二種類のダッシュボード。カスタマーサポート担当は、わざわざSaaSのウェブサイトにログインしなくても顧客情報をすぐにチェックできます。私たちのウェブサイトの顧客情報内に、すべてを組み込みました。

バックエンド開発者たちの多くは今でも、2010年時点のDrupal 7コアのAjaxシステムにこだわっているようです。

マルチステップのAjaxインタラクションをDrupalのコア機能だけで作ろうとしたらどれだけ複雑になることか。とても面倒を見きれないコードになってしまうでしょう。そう、ctools_wizard_multistep_form()とajax_render。君たちのことですよ!

それと同時に、Drupal開発者たちは最近のUI要件にも追われています。しかし、モダンなJSフレームワークがどんどん複雑化するのにおびえているかもしれません。ちょうど、去年の私がそうでした。教えてあげましょう。インターフェイスを改良するのなら、いまVue.jsを導入するのがいちばんよい方法です。Vue.jsを/sites/all/librariesに置いてdrupal_add_jsでテンプレートに追加したら、すぐに使い始められます。フォームを含めてクライアント側を完全にVueで動かしたときに、hook_menuにあるピュアJSONのコールバックを管理するのがいかに簡単になるか。きっと驚くことでしょう。

Yii2で使うVue.js

興味深い事実:Yiiの作者は中国人のQiang Xueです。つまりYii+Vueの組み合わせは、発音しづらいものどうしというだけでなく中国産どうしというつながりもあるのです :)

新しいバージョンのQwintry.com(まだ公開されていません)ではYii2を選択しました。今のPHPフレームワークの中で最高かつ最も高速なもののひとつだと考えています。最近はやりのLaravelほどには有名ではありませんが、私たちは今のところ、Yii2スタックに満足しています(とはいえ、Laravelのこともときどきチェックしています。いい仕事をしてますよね)。

Yii2とPHPで生成するHTMLの量を徐々に減らしつつあり、RESTバックエンドのほうに力を注いでいます。このバックエンドがクライアントサイド向けのJSONを生成するのですが、そこで使っているのがVueJSです。Active Recordモデルに沿ったAPIファーストのアプローチです。

私たちはAPIを重視しています。だからこそ、たとえ内部的に使うだけのAPIであってもきちんと時間をとってドキュメントを作るようにしています。

PHP 7と最新のMySQLを使ったYii2版JSONバックエンドのレスポンスタイムは、Node.jsのバックエンドと大差ありません(だいたい15から20ms程度です)。私たちのニーズを十分満たしており、Drupalを使っていたときの想定よりも10倍から20倍は高速になりました。古きよきPHPなので、さまざまなcomposerライブラリを活用できるし安定したコードベースが手元にあります。

というわけで、Yii2とVue.jsの組み合わせは最高で、コードも楽しく書けています。

他に、内部的なプロジェクトの多くにもVue.jsを採用しています。

まとめ

この3か月、さまざまなプロジェクトで毎日のようにVue.jsを書いてきましたが、どれもめざましい成果を上げています。バックエンドの世界では特に何が起こったわけでもありませんが、JSの世界はこの3か月で何かが変わりました :)今後が楽しみです。

Evan Youが正しい道を歩む限り、今後1〜2年の間にVueがJSフレームワークのトップに立つのではないかと期待します。少なくともバックエンド方面や、小規模なフロントエンドチームの間ではそうなるでしょう。2017年のうちは、まだReactスタックのほうが有力だと考えます。特にReact Nativeが今のペースで改良を進めて成熟していくならそうなるでしょう。

更新:この投稿がHackerNewsのトップページに載ったことで、200以上ものコメントがついて有用な議論ができました。こちらで確認できます。さらにRedditのwebdevでもトップになって、60以上のコメントがついています。