1 comment | 0 points | by WazanovaNews 約4時間前 edited
コンポーネントベースのviewレイヤのライブラリであるReactを、実際に使ってみた感想についての発信が増えてきているので、まとめてみました。(4)はFluxの話も入ってます。)
1) Reactとは?
E4E Developer Conf 2014の講演でFacebookのBen Andersonは、Reactを採用しているサービスを挙げています。
Facebook / Instagram / GitHub (Atom) / Khan Academy (with Backbone.js) / Mozilla Firefox (for Paneis) / NY Times / Reddit (store)
また、その仕様の背景については、
既存のフロントエンドのソリューションは、複雑な双方向データバインディングになりがちなのに対して、サーバレンダリングモデルは常に一方向へのやりとりなので安定している。その安定性 + フロント側でのインタラクティブ性 + パフォーマンスを担保するために、immutableなデータ構造 + フレームごとにDOMの変更される箇所のみをリフレッシュするバーチャルDOMというかたちなった。
2) Let’s Code JavaScriptでのJames Shoreのレビュー
ロックイン
- Reactを採用すると、UIをイチから書き直すことになる。抽象化レイヤで隠す実践的な方法はない。そういう意味では、完全なロックインにはなる。
- しかし、Reactのデザインでは、アプリロジックをUI外に置くようになるので、将来Reactを使わなくなくなったとしても、少なくともアプリを全部書き直すことにはならない。ReactのAPIでやり取りしなくていけない箇所は多くないので、Reactをアップグレードする際にアプリが壊れる心配は少ない。
- ページのサブセットをターゲットにできるので、採用するにしても、後から取りやめるにしても、段階的に移行することが可能。
アーキテクチャ
- ライブラリであって、フレームワークではない。よって、アプリのアーキテクチャ全体が独裁されることはない。既存のアプリコードとの共存も問題なかった。 関連するアーキテクチャパターンのFluxもあるが、完全にオプションなので影響なし。
複雑さ
- 「あー、やっと意味がわかった。」というような複雑さはない。理解しなくてはいけないのは、いくつかの用語(propsとstateの違い)、キーコンセプト(stateの管理の仕方。レンダリングのメソッドをimmutableとして扱う)、コンポーネントの実装に使う典型的なメソッド。
- バーチャルDOMは、壊れやすい抽象化になる可能性はある。新しい、もしくは上級者向けのDOMの機能を使う際は、Reactと格闘しなくてはいけなくなるリスクがある。CSSアニメーションやfocus管理などは要注意。
- 現在のところReactは、進化し続けるブラウザの機能に問題なくキャッチアップできているが、将来的にReactがもうホットなプロダクトでなくなった際、常に最新のものに対応できるかどうか。自分のアプリのライフサイクルを考えて判断すべき。
テスト
- まだテスト周りは未成熟ではあるが、テストAPIは一通り必要なものが揃っている。コードに問題があれば早めに落ちてくれるユーティリティファンクションを使って、コンポーネントのレンダリング、DOMツリーの検索ができる。イベントのシミュレーション、コンポーネントのモックも可能。
- ドキュメントはまだ十分ではない。コンポーネントを比較する方法が見つけられなかった。シンプルなアプリでも、コンポーネントの中にコンポーネントを含むことはよくある。テスト側が、コンポーネントの実装を理解しなくてはいけない状況は避けたい。例えば、自分のApplicationUiコンポーネントはStockMarketTableコンポーネントを含んでいる。アプリの設定が変更されれば、テストにはテーブルの更新をチェックしてほしい。そのためには、実際のテーブルとハードコードしたexpectを比較したかった。結局は、Reactのプライベート実装を調べて、コンポーネントを静的HTMLにレンダリングし、結果を比較した。それでどうにかワークしたが、失敗するとReactの内部実装に依存するエラーメッセージがでて、内容を調べるのに、ReactのランタイムオブジェクトグラフをObject Playgroundでビジュアル化しなくてはいけなかった。他にもっといい方法があるべきだと思うが、見つけられなかった。
- サンプルのテストコードを見ると、シンプルでストレートに理解できるのがわかると思う。
検索エンジン対応
- バーチャルDOMのおかげで、Node.jsを使ってサーバサイドでレンダリングできる。検索エンジンとリアルのクライアントで一つのレンダリングパイプラインを共有でき、新しいページもすぐに表示することができる。document readyイベントや、JavaScriptの読込みを待つ必要はない。静的なマークアップをもち、クライアントコードなしで実行することも可能。
3) ArkencyのRobert Pankoweckiが学んだこと
Google AnalyticsのデータをベースにしたUIをReactでつくった際、ドキュメントでは確かに記述されているが、詳しい理由がなかったので、Reactはコンポーネントが同一かどうかをキーで判断していることに気づかなかったことを紹介してます。
- 例として、国名と都市名のそれぞれのcomponentを用意。国名をクリックするとアクティブになって、都市のリストがリフレッシュされ、その国の中にある都市名だけが表示される仕組み。しかし、最初のリストの中で例えば二番目の都市を選択すると、次に、別の国を選択した際に表示される新しい都市リストにおいても、二番目の都市が選択された状態になる。(簡単なデモ)
- 当初は、「プロパティはimmutable、stateはmutableなので、もしコンポーネントのプロパティを変更したら、Reactは新しいコンポーネントだと把握し、それを作成 & getInitialStateをコールする。」と考えたが、間違っていた。
- Reactは同じコンポーネントかどうかをキーによって判断している。キーは、自動的に増えるintegerで、
data-reactidに表示されている。propが変更されても、Reactはそれを関知しないので、コンポーネントは従前のステートのままになる。TabListにおいて、key: this.state.currentCountryと指定することで対処。
原文に一連のコードがあるので確認ください。
- 「そもそもステートをコンポーネントにキープしてなければ、この問題は起きない。」との指摘をもらった。
- コンポーネントの階層の中で、ステートを上位の階層にもっていくのが賢明。リスト自身がステートを保持する必要はないのではないか。リストのエレメントがクリックされたかどうかは、親コンポーネントの関心でもある。何か新しいものが選択されると、リストが変更 & レンダリングされるだけでなく、親コンポーネントのUIもいずれにしても変更することになるのだから。
- 今回の学びは、
- 子コンポーネントにキーを付与する。
- ステートは1箇所で管理する。
- 動的なステートレスコンポーネントは簡単で、キーなしでもどうにかなる(付与するのがベストプラクティスで、Reactは警告してくれる)が、ステートフルコンポーネントではキーは必須。
4) ReactとFluxについてのFacebookのIan Obermillerのインタビュー
Ian Obermillerは、Reactの開発チーム所属ではなく、FacebookでReact + Fluxを1年以上使っているユーザ側の人物。これまでの経験について、インタビューに応えています。
- リモートコールとローカルコールについては、まずデフォルトは全てが非同期。オペレーションがリモートだろうがローカルだろうがフレキシブルにできる。コンポーネントがデータをリクエストしていれば、読込みがいつ始まって、いつ完了するか知りたいはず。更新中であればスピナーがだせるし、もしくは無効にして再度更新するとか。次はいつ読込むとか。
- writeは全てaction。actionは呼び出すファンクションのこと。APIと直接話し、全てのハンドリングをする。コールバックやpromise handlingのように。サンプルのチャットアプリのように、actionを発行し、storeがactionを待ち受けする。一方、readは、コールバックもしくはpromiseベースのAPIで、コンポーネントがstoreとダイレクトにやり取りする。
- writeでは、データのペイロードとともにactionを発行し、readではstoreを直接呼び出す。データを取得するactionはなくて、storeが全て取得してきてくれる。キャッシュからかもしれないし、直接DBからかもしれないし、複数のサーバコールをしているかもしれない。
- storeがかなり複雑になることが多いので、このやり方でやるように変わってきた。当初は基本的に全てがサーバで、そこからページ関連のものを読込み、ソーティング、フィルタリングして、無効にするものもあって、クライアントにたくさんのキャッシュロジックを持たせ、主にstoreで対応した。storeは、いつものが返ってくるか教えてくれて、特定のアイテムをレンダリングしたことを知っている。誰かがリストのアイテムを取得したら、そのアイテムを更新。storeは変更したアイテムや再レンダリングしてよいかわかっている。様々な責任をもっていて、かつとても便利な存在だとわかった。storeは全てプロパティのバッグであり、fetchロジックを追加するとうまく機能した。
- FluxでDAO (Data Access Objects)と呼んでいるのは、実は単なる特定のオブジェクトのAPIモジュール。まずは、一般的なAPIモジュールがあって、その上のレイヤで、オブジェクトのタイプごとに特定の種類のコールができるようになっている。例えば、IDごととか、IDのリストごととか。アイテムのlikeかどうかなどの条件も付加できる。
- storeがデータをfetchするときはコールバックを使っているが、writeは全てactionを介している。これが、actionがDAOを直接コールしている理由なので、トリッキーな話になる。「これを更新中...ちなみに成功、ちなみに失敗...」というようなシーケンスイベントを発行することもできる。コンポーネントが、特定の正しいコールが成功したか、失敗したか知らなければいけないというところがトリッキー。それは大変。最近よく使いはじめたパターンは、「メッセージ更新成功」というようなトリガーに対して、コンテキスト変数をもたせること。コンテキストは、例えば「writeを発行する」とか。
- 例えば、Facebook上で一番人気のあるTop10ユーザと人気のないワースト10ユーザのリストのように、storeに二種類の異なるデータを持ちたいが、そもそも全ユーザのリストを持ってないときにどうしているかというと、フィルターやソートプションなどをもつことができるクエリAPIや、複数のAPIを使ったstoreなど。そのAPIから単純にfetchしてくるパターンだけでなく、ユーザのリストをどうキャッシュするかまで理解しているレイヤもある。コンテキストの例としては、人気のユーザのフィルター、人気のユーザのソーティング、昇順、降順など。基本的には、全てのユーザオブジェクトのキャッシュと、それからクエリしてつくったユーザIDのリストのキャッシュの二種類を持つ。
- アプリ側でキャッシュのパージはしてなくて、ページを再読込みするまではそのまま。キャッシュを無効にはしている。例えば、likeの数でソートしたものをやり直すときとか。
- 将来直したいと思っているが、必ずしも、ユーザとかポストとかタイプごとに一つのstoreというかたちにはなってない。現状では、同じリクエストでAPIは依存するデータも取ってくることができてしまう。例えば、メッセージのデータにおける fromとtoのフィールドはいずれもユーザオブジェクト。内容は名前だけかもしれないが、IDがキーになっている。理想的には、ユーザはユーザstore、メッセージはメッセージstoreとしたい。APIからメッセージを取得すると、ユーザオブジェクトを拡大して、メッセージリストはユーザが付加されたかたちで返ってきてしまう。今のところはオブジェクトとしてキープしている。通常はディスプレイ用にしか使ってない。いつかやりたいのは、メッセージのリストをfetchしてきて、ユーザがくっついていたなら、ユーザstoreにプッシュして、キープしない。メッセージstoreからの参照が以外では。IDをキーにするだけとか。けど、実際に参照の関係はないかたちで。
- 全部ES6。ES5にコンパイルダウンできないサブセットは使っていない。
- Reactにおいて回避策がないのは、focusやblurなどのDOMメソッド。DOMノードに手を入れるしかないが、refsを使うと比較的簡単にできる。
5) Reactのコードレビュー
同じ単語が書かれたカードを当てる簡単なゲーム(デモ)のコードを、Ian Obermillerがレビューしてます。
コメントの詳細はこちら。
主なポイントは、
- コンポーネントを渡しまくって、そこでメソッドをコールしないこと。代わりにpropsを渡すほうがよい。
- 隠すよりも条件付きのレンダリング。
- JSX harmony transformを使う。
- propを宣言すること。
- classSetを使うこと。
- setStateは、変更しようとするpropだけを含めればよい。
#react #flux