こんにちは、編集長の白石です。
本記事は、2016年9月に開催されたTechFeed Live#2 「React vs Angular 2」の模様をお伝えする記事の後編です(前編はこちら)。 TechFeed Live#2とは、「TechFeedを地上に出現させる」ことをコンセプトとした、テクノロジーの最新トレンドをエンジニア向けに紹介するというイベントです(TechFeedとは、「最先端が、ここにある。」をキャッチコピーとしたエンジニア向け情報収集アプリです)。本イベントは、ReactとAngularをより楽しく深く学ぶため、現代のWebアプリに求められる各種要件についてそれぞれを比較する…というアプローチを取りました。 (私は本イベントの企画と対談のモデレーターを務めました)
読み応えバッチリ、勉強になること間違いなし。今回はライブ感を重視し、あえてイベントでの(砕けた)口調も可能な限り再現してみました。 では皆様、どうぞお楽しみください!
トークバトル参加者(順不同、敬称略)
(編集部注: 本記事では登壇者の皆様の希望により、以下ハンドルネームで呼称させていただきます)
React Side
Angular2 Side
モデレーター
- shumpei (白石 俊平) (HTML5 Experts.jp編集長)
また今回、フレームワークを比較するための項目を以下のように洗い出してみました。基本的にこの流れに沿って進めたいと思いますが、脱線は大歓迎ですので、皆様ご自由にご発言いただいて構いません。(編集部注: 後半は「スタイリング」から)
開発⾔語アーキテクチャビルドツールルーティングテンプレート- スタイリング
- コンポーネント以外の処理
- ツールサポート
- テスト
- パフォーマンス
- サーバサイドレンダリング
スタイリング
shumpei 次はスタイリングについてお話いただきたいと思います。コンポーネントにスタイルを当てる云々の話、ですね。 では、Reactサイドから伺いましょうか。ReactのCSSサポートの現状といったところからお聞かせ願えるとありがたいです。
yosuke_furukawa CSS in JSとかそういったあたりの話ですね。CSSをJSの中にimportして、JavaScriptのオブジェクトかのように、コンポーネントにスタイルを当てることができる機能があります。 厳密なCSS in JSは、style属性に直接値をぶち込みます。そうすると、上からのCSSの影響を相当受けにくくできる。 そこまでいかなくても、CSS Modulesというのがあって、クラス名をuglify(難読化)してかぶらないようにして、中ではそのuglifyされたクラス名を使ってスタイリングを行い、外からの変更を受けにくくするというやり方もあります。
そういう感じで、CSSとJSを一体化して扱うということは、React界隈ではわりとやられています。
shumpei React本体はスタイリングとかに関する機能とかは持っていなくて、外のライブラリを使って実現するという感じなんですね。
koba04 はい、React本体としては、CSSは属性の一つ(style属性)なので、特別なサポートはそれほどありません。オブジェクトで値を指定できたり、幅や高さの単位を省略して指定できたりするくらいです。 あとはwebpackとかの外部のエコシステム上で、それぞれ好きにやっているという感じです。
shumpei では、Angular 2サイドはいかがでしょうか?
laco Angular 2は、@Component
デコレータの中にstyles
という属性があって、そこに書いたスタイルはコンポーネントの中にしか効かないようになっています。Shadow DOMと同じ動きをするようにエミュレートされています。
yosuke_furukawa どうやってエミュレートするんですか?
laco えーと、BEMみたいなのを自動的に生成します。それでかぶらないようにする。まあ、CSSをインポートすることはしないですが、文字列で指定してるので結局インポートしてるのとあまり変わらないというか…あんまり変わらないですね(笑)やりたいことは(Reactと)一緒です。
だけど違うのは、設定を一つ変えるとネイティブのShadow DOMを使えるように変えられます。現在のデフォルトはエミュレーションになってますが、将来的にShadow DOMに対応したブラウザが増えた時に、さくっと変えられるようにはなっていますね。
koba04 この辺 (スタイリング) は結構、開発メンバーの中にデザイナーさんがいるのかとか、JSがメインの人ばかりでやるのかとかで、どうCSS書くのかが変わってくると思うので、選択肢がいろいろあったほうがいいんじゃないかな、とは個人的には思っています。
副作用を伴う処理
shumpei では次に行きましょう。APIコールなど、副作用を伴う処理をどう扱うかは、フレームワークごとにかなりアプローチが異なってくるところだと思いますが、そこら辺の事情を伺わせてください。
laco まずAPIコールでいくと、Angular 2だとHttpというライブラリがあって、それを使うという話になります。
koba04 今「ライブラリ」という言葉が出ましたが、(APIコールをするのに)外部のライブラリを使うという話なんですか?
laco npm的には別パッケージになってるんですが、Angular 2の一部という扱いです。サブパッケージと言ってもいいです。
koba04 そこをフレームワークが直接サポートする必要って何かあるんですか?
laco (Httpライブラリ内で) DIをガリガリ使っているというのと、Angularはフルスタックなフレームワークなので、アプリケーションを作るのに必要なものはとりあえず揃えておくというポリシーですね。
83 あと、Angular 2のHttpをDIで使っておくとテストが楽、モックのAPI Callを自分でガリガリ書いたりする手間が省けるというのが利点かなと思います。
shumpei ちなみに個人的には、Reduxをちょっと調べた時にミドルウェアでAPIコールをしているのが「難しいな」と思ってしまったんですが、ああいうのは慣れれば全然問題なくなるもんなんでしょうか。
yosuke_furukawa そこで難しいな、と思っちゃうのはまだReduxに慣れてない証拠です。
shumpei ほう。
yosuke_furukawa Reduxっていうのは基本的に副作用をどんどん外していってるんです。APIからGETして取ってきたデータをステートに反映するというのは、これは副作用そのものなんです。 で、こういう副作用をどこでやるかと言ったらミドルウェアでやる、というのがReduxの基本的な考え方なんです。 そこに慣れると、みんなミドルウェアで書くっていうのがなんとなくわかる。そこに慣れてない人は、Reduxをフレームワークだと思って使っちゃうんです。
shumpei なるほど、基本的な思想がちゃんと理解できていなかったんですね、ぼくが。
yosuke_furukawa あとReduxミドルウェアと言ってもいろいろあって、redux-promiseだ、redux-thunkだ、redux-observableだ、redux-sagaだと、ぼくはredux-effectsっていうのを使ってるんですけど。 要は、副作用が必要なときに副作用を発生させる方法っていうのはたくさんあるし、非同期の処理の仕方一つ取ってもたくさんあるわけです。 ES6のジェネレーターでやるっていうのがredux-sagaだし、そうじゃなくてPromiseでいいんじゃないの、っていう人はredux-thunkだとかredux-promiseだとかを使う。
そうじゃなくてもうちょっと高次元なことをしたい、複数のイベントを包括的に扱ったりしたいという場合はObservableが必要になってくる、と。 それはどこまでデカい規模のものを作るかによってミドルウェアの選定をしなくてはならなくて、そこをツライと言ってしまうとちょっと(Reduxを使うのは) 厳しいかもしれません。
フレームワークなのかライブラリなのかという話でいくと、昔あったAngularJSのMVWhateverだったりなんだったりっていうものをReduxの何かで置き換えられるかというと、あんまりそんなことやってくれないんですよね。 結構薄いライブラリなので。フレームワークじゃないとぼくがずっと言っているのはそういうことです。
何かおまかせできるわけじゃなくて、自分たちでシンプルなAPIをたくさん作って、やってくれるのは状態の管理とイベントの管理だけ、というのがReduxの思想というか。 で、副作用を徹底的に省いてくれる。
でもみんな気になってるのは、その副作用をどうやってやるの、というところで、フレームワークを使いたいと思っている人にとってはそこにミスマッチがあるんじゃないかと思っています。
テスティング
shumpei では、テスティングについてはいかがでしょう?
yosuke_furukawa コンポーネントのテスト書くか書かないか、って話があるんですが。書くか書かないかをまずはお聞きしたい。 書くとすると、こちらはenzymeっていうツールを使ってます。
laco こっちはKarmaっていうのを使います。KarmaはAngularチームが作っているテストフレームワークです。なので、KarmaがAngularと一番相性のいいテストフレームワークとして使ってますね。
あと、Angularがテスティング用のAPIを内包しているおかげで、コンポーネント単位でのテストができるようになってます。 たぶんenzymeとそんなに変わらないと思います。
koba04 そうですね、enzymeはいろいろラップしていて、DOMにマウントしたコンポーネントに対するテストとか、renderToString()
で書き出したHTML文字列に対するテストとか、対象のComponentだけを浅くrenderするShallowRenderer
などを提供しています。
そういうのを提供するために、ReactはTestUtilsというアドオンを提供しています。最近だとTestRendererっていう、ツリー構造をただのJSONで返してくれて、それに対してテストするみたいなのもあります。なので、テストは書きやすいかな。 あと、Reactのコンポーネントって多くはただの関数で、状態を持たない。入力を与えたら、React Elementのオブジェクトが返ってくるというただの関数なので、テストも簡単に書けるという感じです。
Reduxとか使うと、そのただの関数の部分がReducerだったりと増えてくるので、より簡単に書けるとは思います。 あとShallowRendererだったらDOMにも依存しないので、JSDOMなどを使う必要ないし、NodeとかでMochaとかPower Assertとか使って簡単にすぐかけるというのはいいところかなあと思います。
laco 書くか書かないか問題で行くと、Angular 2の場合は書かないとまずいんですよね。テンプレートが間違ってた時に本番でぶっ壊れるので、テンプレートがコンパイル可能かというのを、コード生成可能なテンプレートになっているかというのをテストしないとまずくて。JSXの場合はコンパイルエラーになってくれるけど、Angular 2のテンプレートはただの文字列なので、実行するまでわからないというのがあって。
yosuke_furukawa ただ、そもそもビューのところって変更が一番多いところだと思うんです。そこってテスト書いちゃうと、ROI (投資対効果) 的にというか、コストがペイしないんじゃないかとぼくは思っていて。なので、基本は「書かない」がいいんじゃないかと思ってます。
laco コンポーネントの中のプロパティのテストとかですか?
yosuke_furukawa そう、それとか…。
laco だったら、基本やんないですね、あんまり。
yosuke_furukawa lacoさんの言っているテストっていうのは、テンプレートがコンパイル可能かどうかの(最低限の)テストってことですか。
laco そう、それは全コンポーネント通しておかないと夜も眠れない(笑)。
yosuke_furukawa それは、たしかに(Reactは)JSXはコンパイル時にわかる。
laco それに、一口でコンポーネントっていいますが、そっちは関数ですが、こっちはステートを持つやつなんで、性質が全く違います。
shumpei AngularのほうはDIでテスタビリティを向上させている。一方でReactにはDIとかそういうのはないけど、Reactのコンポーネントはただの関数なことが多いからテストしやすいと。
yosuke_furukawa Reactのコンポーネントは基本的にステートを持たないから、そこ(DIとか)に関心がない。性質の違いですね。
パフォーマンス
shumpei 次はパフォーマンスです。これって議論になるのかな?
laco 今はもう、あんまり(どちらのフレームワークのパフォーマンスも)変わらないですよね。Angularのほうが初期ロードはちょっと遅いかもしれないけど、動き始めてからはあんまり変わらない。
83 これ、AngularJSでアプリ作ってて、Angular 2でもアプリ作ってて、って経験から言うと、AngularJSだから遅い、ってAngularのせいにできたんですよね。「ここ遅いのなんで?」って言われた時にAngularJSのせいです、って言っちゃえるという甘えがあったんですけど。
最近Angular 2とReactほとんど差がないんで、自分の書いてるコードが酷い可能性のほうが高い。自分の書いたコードを疑えって言う。最近パフォーマンスというか、自分がどれだけ最適化されたコードを書けるかとか、遅かった時にどう測定してどこを最適化していくか、っていうところにかかってるんじゃないかなと思います。
laco (Reactは)たぶん、ミドルウェア書けば書くほど遅くなりますよね。Angularも、いろいろやればやるほど遅くなる。DOMの書き換えパフォーマンスはどちらも変わらないと思います。
yosuke_furukawa ただ、DOMの書き換え周りって、仮想DOMとか変更検知のアルゴリズムとかも関わってくる話ですよね。変更検知については、Angular1の頃は、自分でイベントループみたいの持っててそこでダーティチェックしてたけど、2はどうなってるんですか?
laco Zone(.js)の話になるんですけど…Zoneの話長いんで(笑)。 ざっくりいうと、windowをモンキーパッチして、
yosuke_furukawa モンキーパッチ!?(笑)
laco setTimeout()とかXHRとか非同期イベントが起きた後は必ずAngularのイベント呼ぶようになってる。
yosuke_furukawa マジかよ…めちゃEvilじゃないか…!(笑)
laco でもそれがTC39にZoneとして提案されていて、すでに(ステージ)0にもなってるんですよ(笑)。 (編集部注: ECMAScriptの仕様策定は、アイデアレベルの提案がステージ0に始まり、成熟するに従ってステージを登っていく)
だから…あれ、何の話だっけ。ああそうか、変更検知のイベントループの話だ。(Angular)1の頃はループガンガン回してたけど、それはもうないです。何も起きてないときは何も動かない。何かしらクリックしたとかがあったら、イベントが走って、windowからAngularにお知らせが来る、というふうになってるんです。
koba04 パフォーマンスっていう意味で言うと、Reactは基本やることが少ないし、やることが明確なので、チューニングはしやすいのかなあと思います。
react-addons-perfとか使って計測して、パフォーマンスチューニングのポイントもshouldComponentUpdate
を実装するか、renderの中をちょっと見直すとかなので。
laco しかし、仮想DOMは速かったけど、Angularも追いついちゃいましたね。しかも仮想DOMなしで。結局仮想DOMってどうだったのかなあ…と。「ポイント、そこじゃなくない?」っていう(笑)。 (筆者注: ここ、lacoさんがイベントを盛り上げるため煽ってくれてます)
koba04 まあ、仮想DOMだからこそ、ただの関数みたいな形でコンポーネントを簡単に作れるようになったので…。
laco でも、最近Pure Componentって入ったじゃないですか。Shallow Rendererとか。 あれってどうなんですか?この間ものすごく疑問に思って。Pure Componentにすると、オブジェクトの状態を自動で見て、自動で変更検知してくれるってやつですよね。 あれって、Reactがやりたかったことなんですか?
koba04 あれは、オブジェクトツリーがでかくなると仮想DOM比較のコストもバカにならないので、その比較すらも飛ばしたい時に使うっていう感じです。
laco 仮想DOMのアルゴリズムが敗北したってことじゃないですか?(笑)
shumpei 煽る煽る(笑)。
koba04 基本的にはそこまで必要とするものでもなくて、よっぽどパフォーマンスが求められる場面で使われるものかなと思っています。
yosuke_furukawa ぼくとしては、パフォーマンスのチューニングポイントが増えたって話だと思ってます。仮想DOMの敗北については、次のサーバーサイドレンダリングでぼくが話をするんで、まあここはこれくらいでいいんじゃないかなと(笑)。
会場 (笑)
サーバーサイドレンダリング
shumpei じゃあ、サーバーサイドレンダリング(SSR)について、yosuke_furukawaさんどうぞ(笑)。
yosuke_furukawa そうですね、実際にSSRでもコンポーネントのツリー構造を作って、そのツリーに対してReact DOMのIDを割り振るっていうような「仮想DOMを構築する処理」が入るわけなんですが、それって普通のテンプレートエンジンだとまずやらない処理なんですよね。普通のテンプレートエンジンはHTML生成すればいいだけだから。効率化されているんです。
そこをReactのrenderToString()とかでやると、まあ、そこまで効率化されていない。 もしかしたらキャッシュとか持って、効率化する余地はあるのかもしれないんだけど、今のReactだとそこまで効率化されていないので、やっぱりテンプレートエンジンとしてのスピードを見るとそこまで速くないんです。 そこがちょっとね、どうしたもんかね、と。
ぼくが今やってるやつだと、1レンダリングするのに50ミリ秒とかかかっちゃって、しかも50msecかかった時に、Node.jsなんでイベントループが止まっちゃってるんですよね。だから由々しき問題、由々しき事態なんです。 だからそこは、非同期処理にしてイベントループを止めないようにするか、キャッシュですごい速くするか、そもそもDOMツリーを構築するところをなんとかするかと言ったことが必要なんですけど。
koba04 そういう意味では、Reactってサーバーサイドレンダリングできるんですけど、正直あれなんで入ってるのかぼくはよくわかってなくて(笑)。Facebook自身も使ってないですしね。 中のアルゴリズムを見ると、クライアントのDOMを作るときと同じコードを使っているので、それは当然文字列作るのと比べれば効率悪い。 そこはもしかしたら新しいアルゴリズムが入ることで効率化するのかもしれないけど、たしかに今だとパフォーマンスが問題になるのは明白な感じの実装にはなってます。
yosuke_furukawa Netflixとか、いろんな企業が実際にサーバサイドレンダリングはやってるんですけど、初期表示のレンダリングするところだけサーバサイドレンダリングして、 あとはスクロールするたびにちょっとずつクライアントサイドレンダリングしてたりする。それって地獄じゃねえのか、と(笑)。 そういうことが簡単にできるライブラリとかが別途あればいいんだけど、今のところまだないから、そういう意味だと、仮想DOMは敗北に近いかなと。サーバサイドレンダリングするなら。
shumpei Angular 2のサーバサイドレンダリング事情はどうですか?
laco Angularのサーバサイドレンダリングは…たぶんあんまり変わらないですよ。基本的に遅い。ただ何がいいかって、ルーティングとかHttpとか、Node上でも動くように全部パックされてるという、フルスタックならではの安心感はありますね。
ルーティングが済んだ状態のSSRも簡単にできるし、SSRするときに最初にAjaxが必要だったら、Node上で動く@angular/httpみたいな、ユニバーサルなやつが提供されている。それを使えば、変更検知された状態のSSRがちゃんと出ます。全部レールに乗ってるっていう、そこらへんの安心感はありますね。
ライブラリか、フレームワークか
shumpei はいでは、お時間も迫ってまいりましたので、そろそろ終わりにしたいと思います。
83 あ、一ついいですか?ちょっとこのイベント自体に苦言を呈したくて。
会場 (笑)
shumpei え、なんでしょう?
83 ReactとAngularっていうのを比べるのって、みんな大好きですよね。でも、ほんとはこれって比較できるもんじゃないんですよ。型が一致しないものは比較できないんですよ。
会場 (笑)
83 で、比較できないものを比較しているというのは、こういう項目を挙げてやっているっていうのは、フレームワークの相互プレゼンテーションみたいな感じで、Reactはこうですよ、Angular 2はこうですよ、という感じでやってるんですけど、ほんとにそれがみんな聞きたい話なのか?って思うんです。
私はlacoのほうからオファーを受けて呼んでもらったんですけど、こんだけすごいパネラーが揃ってて、順番にフレームワーク・ライブラリ紹介合戦みたいになってて。これじゃないでしょ、って思ってるんですよね。 もっと、フロントエンドのエンジニアとして、Web APIがどういうものがあって、いつ何を使うか、どう組み合わせるかっていうのがやっていくべきことなのに、何年かしたらすぐ廃れるようなライブラリの議論なんてのに、似非プロレスなんてのをやっていても、これは違うんじゃないかなあ、と思うんですよね。
shumpei すいません、、反省します。
83 いえいえ、これ、言っとかないと終わってしまうな、と。すいません。
yosuke_furukawa いえいえ…最高です(笑)。