Code

2016年6月5日日曜日

Angular から React.js への移行

 昨日今やってるプロジェクトと似ている新しいウェブアップがリリースされました。AngularJS 1.0 を使っています。マネージャーたちは使ってみたら、なぜうちのプロジェクトと比べると、遅いと感じられるかと聞かれました。そもそも AngularJS 1.0 は重いからです。ぐるぐる回るロードアイコンが止まったり、なかなか応答が返ってこなかったりしたので、そう聞かれても仕方がありませんでした。
 じゃ、どうやって、その AngularJS プロジェクトを React.js に移行するかと考えました。
 まず、AngularJS 1.0 で ng-repeat を使ってるところを directive にして、React.js を使ってレンダリングしたほうがいいと思います。原因は ng-repeat は超を付くほど重いからです。key を使っても、ajax から返ってきたデータは毎回違うから、HTML の reuse がほぼ不可能です。逆に、React.js の場合、DOM ノードは Virtual DOM をベースに更新されるので、データが変わるなら、その分のみ更新されます。DOM ノードはそのままを使うケースが結構あります。
 具体的には、ng-repeat の Collection を prop として、Component に入れます。その中で、Rendering する。directive の作り方は:
.directive('react', () => {
  return {
    scope: { data: '=' },
    link: function (scope, element, attrs) {
      scope.$watch(data, onDataChange);
      function onDataChange () {
        React.render(
          React.createElement(REACT_COMPONENT, { data: data }),
          element[0]);
        };
     }
  }
});

 これで、data が変更するたびに、React.render が呼び出されます。Virtual DOM を使っていますので、複数 rendering しても、変更する部分だけ更新されます。

 次は ng-include や ng-view を使ってる部分を切り出して、React.js に個別にレンダリングします。ReactDOM.render 関数や、React.js 自身は同じページに複数の Component をレンダリング機能がサポートしています。ng-include などは独立な Component らしいので、controller の関数を React.js Component に入れて、データバインディングを完成すれば、すぐ使えます。
 基本的に、service や、factory 部分を Flux や Redux の Store に変わるので、そんなに変更はないはずです。ただ、$http みたいな機能は React.js 提供してないから、qwest など別のライブラリを使ったほうがいいでしょう。これを考えると $http の promise が resovle されたら、$rootScope.$applyAsync 関数が呼ばれますので、ページ全体の Watcher が2回呼ばれます。。。重いですね。

 最初の一歩は一番難しいですが、変更するたびに、どんどん新しい React.js Component もできるので、やりやすくなります。最近は React.js Develop tool を使って、他のサイトはどうやっているかが見れます。
  Chrome React.js Developer tool

 例えば Airbnb などは一つ一つ Component を作って、画面に入れ替わっていることが見れます。

2016年5月22日日曜日

React.js での DOM 操作

 React.js を使うと、よくある DOM node に CSS クラスだけを設定したり、その DOM node の高さを変えたいときがあります。
 DOM 操作をする場合、componentDidMount、と componentDidUpdate 二つ箇所を考えなければなりません。最初 Component が render する場合、React.js は Virtual DOM を作成して、実際の DOM に追加されたら、componentDidMount 関数が呼び出されます。その中に、findDOMNode(this) を使ったり、this.refs.REF を使って、DOM node へのアクセスは可能です。また、component に新しい props が設定されたり、setState を読んだりすると、componentDidUpdate が最後に呼び出されます。Mount される時と同じ、DOM nodeを取得して、操作できます。
 親と子の関係について、まず子の didMount と didUpdate 関数が呼び出されて、最後に親の関数が呼び出されます。

 もし親に CSS クラスを追加する場合、setState を使うと、全 App の子 Component が re-render するので、スピードだけを比較すれば、直接 DOM を操作するほうが速いかもしれません。ただ、DOM 操作を直接やると、Virtual DOM と実際の DOM が合わなくなるので、一回やると、もう関係ある DOM の更新を全て自分でしなければなりません。App 全体の構造から見ると、それは良くないです。オススメできないです。(自分の経験から見ると、逆に面倒です。)
 この場合、shouldComponentUpdate 関数を使ったほうがいいです。まず、各 Component を作るとき、どのような props または state 値変更があったら、更新すべきかを考えたほうがいいです。その値の比較を shouldComponentUpdate の中に書いて、例えば:
    return this.props.PROP !== nextProps.PROP || this.state.STATE !== nextState.STATE;

 そうすると、親が setState を自分の state を更新して、子 Component の PROP が関係ない場合、その子の shouldComponentUpdate が false を返して、Virtual DOM の比較が行われなく済みます。
 React.js はこのような DOM 操作をまとめてします。また Browser もそのような処理が入っていて、できるだけ画面更新を一回でやりたいです。個別の DOM 操作と比べると、そんなに遅くないです。
 React.js も pure render mixin が提供しています。もし自分で shouldComponentUpdate を書きたくない場合、その mixin を使えば、同じ効果が得られます。(props で関数を子 Component 渡す時、関数の Bind を使わないように。なぜなら、bind は毎回新しい関数が返されますので、reference が更新されて、pure render mixin はいつも true となります。)

 AngularJS が最初に出た時、使い方として、directive に DOM 操作すべきとか Think in AngularJS way がありました。 React.js も同じように、できるだけ、React.js にいろんな操作をやらせるべきです。
 Think in React.js way です。

 それでは。

2016年5月1日日曜日

React.js Web App アーキテクチャー

 React.js を使うと、よく変数は Component の props により親からセットするか、State に入れて、操作するかと相談されます。また、アプリ全体として、どのように作ればいいかと度々聞かれます。
 今まで作った React App を例として:
 DOM に Render するとき <ExampleApp {...props} />
    ExampleApp の中に store からデータを取得して、state として保持。
 App 中の Component はできるだけ props を使って、データまたは callback 関数をセット。
 子 Component の中に、props を使って、データを表示したり、親の callback を読んだりする。
 必要であれば、子 Component は state も保持する。

 理由としては、React.js で Component を更新する場合、props をセットしなおす、setState({newstate}) 関数読む二つ手段があります。ExampleApp に store のデータを state に保存しているので、データが変わったら、App の setState を読んで、render 関数がもう一度呼び出して、新しい props がセットされるため、すべての子 Component が Re-render して、更新します。また子 Component 自分の state を更新する場合、子 Component の render 関数だけ呼ばれますので、App 全体のリセットが行われなくて済むので、速いです。

 注意点として、まず、props が使えるなら、props を使うべきです。state の場合、re-rendering が発生する場合、保持されますので、使うときは気をつけないと。
 次、props は readonly であるべきです。props は親から渡されるので、object の 場合、reference なので、もし間違って変更したら、親持ってるデータも同時に変更されますから、次 render する場合、props が変わってないようになったら、Component が更新されない可能性が高いです。
 子 Component から、親に onClick や、他の Component を更新すべきと通知する場合は、親からの callback を呼ぶべきです。
 onClick: function (e) { /* 子 Component 自分の操作 */
                                           this.props.parentCallback(e); }

 では、何を子 Component state に保持すべきかとういうと、例として checkbox とか form control など。その state は自分だけ使う、他の Component はそれを知らなくていいものです。もし同階層の他の Component がその値に従って、変更する場合は、親に state を保持して、props として、二つ Component に渡すべきです。

 それでは。

2016年4月10日日曜日

React.js Dev version Warning の使い方

 React.js を使うと、Webpack か Browserify と一緒に使うのがオススメです。個人的には Webpack のほうがすきです。時々 Dev バージョンに Warning が表示されたりして、Production にはそういう Warning が表示されません。それは
     ("production" !== process.env.NODE_ENV) ? Warning : other-process;
に秘密を隠れています。
 Webpack を使うと、最後の package には、Node みたいに process.env というコードが追加されます。production バージョンをコンパイルする場合、
    plugins: [
          new webpack.DefinePlugin({
              'process.env': {
                    'NODE_ENV': JSON.stringify('production')
              }
        })
      ],
が使うので、NODE_ENV は "production" になります。それで、Warning とかが実行されないようになります。
 実際のコードは Uglify を通すと、false になるところが全部消されるので、production バージョンのコードには残りません。
 通常の開発では、活用できるかなと。
 それでは、また。

React.js のほうがいい、Angular 1.0 と比べたら

 つい、来週新機能のリリースが決まりました。同時に、React.js のほうが、Angular JS 1.0 より良いと思われるようになりました。
 以前、Framework として、AngularJS 1.0 のほうが上だと思っていましたが、React.js 15.0 のリリースより、DOM が綺麗になったことで、React.js のほうが Better になりました。
 まず、data-reactid という属性がなくなりました。次、React.js によりフリーテキストに追加された<span> タグもなくなりました。これは React.js 最後の二つ問題点だと思っていましたから。これで、DOM は前より軽量に、本当の DOM のままになりました。逆に、AngularJS 1.0 の場合、ng-scope など、いろんな要素が追加されて、複雑です。
 もう一つ、最大な決め手は React.js のほうが速いです。以前 AngularJS を使うとき、ぐるぐる回る gif の Spinner 止まったり、アニメーションがカクカクしたりすることが結構ありましたが、React.js の場合、一度も見たことがありません。
 今作ってるサイトは競合サイトが結構あって、そちらは大体 AngularJS を使っています。同じ Spec のコンピューターを使うと、遅いなと思われました。特に Google Maps API で Marker を地図に入れる処理は UI が止まったようです。それは良くない User Experience です。

 さらに、React Native も少し見てみたので、Cordova とか Sencha Touch とかと違って、これは本当の Native App になります。HTML5 Hybrid App 一つの問題は遅いです。React Native を使うと、同じ概念で、本当の App が作れるし、Native のアップだから、多少無駄なコードがあっても、全然問題ないはずです。
 重要なことは一つのライブラリを使って、Web、iOS、Android 全部カバーできることです。さらにサーバーサイドレンダリングもできるので、一つのエコシステムになるような気がしています。
 これから、時間をみて、今使ってる React.js のバージョンを 15.0.1にして、早くアプグレードしようと思います。
 それでは。また。

2016年3月6日日曜日

React.js Modal Component の作り方

 いろいろ忙しいです、最近は。プロジェクトのリリースは一ヶ月伸ばされました。ビジネス側はログイン機能がほしいと言ってきたので、五月のプランに入っている Authorisation / Authentication 機能を3月いっぱいで開発することになりました。そのログイン機能はモーダルをポップアップして、ページの真ん中に表示することになっています。
 ライブラリを使おうと思ってましたが、調べてみたら、React.js 版はあまり内のプロジェクトにあわないなと思いました。それで、自分で作ろうと思いました。
 まず、そのモーダルポップアップは一つになるべきです。つまり一ページの中に一個だけにしたいです。そうすると、children を変えることで、いろんなポップアップが表示できます。次、ページの真ん中に表示するにはまず div とその overlay が必要です。
    <div className={ "modal" + this.props.showModal ? " active in" : "" }>
        {this.props.children}
    </div>
    <div className="overlay"></div>
 もちろん、overlay は pseudo-element でもできます。
 簡単ですね。:)ポップアップするときに、props.showModal を使って、active in class を追加して、画面に表示します。
 そのあとは close ボタンなどを作るだけです。
 まぁ、もし children にクローズボタンとか入れる場合、React.cloneElement を使ったりして、props を追加すればいいですね。

 AngularJS と比較すると、テンプレートベースのモーダルは ng-include を使えば、同じことができます。
 ここで、React.js のいいことは、re-render は速いです。すでにレンダリングした Component をもう一度レンダリングする場合、children の type をまず React.js を比較して、もし違うタイプだったら、ツリー全体を更新しますが、もし同じ Component の場合、更新するだけです。DOM 操作は最小限に抑えています。
 逆に AngularJS の場合、ng-include はテンプレートの string をチェックしていますので、変わってない場合は Dirty Check でスキップされます。つまり、モーダルが更新されないのです。ここで、ng-include を一回クリアするか、$timeout を使って、リフレッシュしないといけません。または自分でその テンプレートの scope 引数を親から変更したりして、更新します。また Dirty Check が一つ増えます。
 React.js の場合、わかりやすいですし、AngularJS 1.0 は面倒くさいなと。AngularJS 2.0 では scope の ダイジェスト対象が設定できるようになるので、速くなると思いますが、やはり、Component という概念は上だと思います。
 来週 Polymer を見てみよう。

2016年2月21日日曜日

React.js もっと速くするコツ

TL; DR:
    React.js Component を作る際:
 1 shouldComponentUpdate 関数は必ず入れましょう。または pureRenderMixin みたいなモジュールを使いましょう。
 2 Component は state を持つであれば、最小限の Component にしましょう。そうすると、setState は最小限 render() 関数を呼び出すし、最小限の DOM 更新します。
 3 App に state を置いて、各 Component はできるだけ props を使いましょう。もしいっぱい props があったら、Babel を使って、 {...obj} みたいなシンタックスを使いましょう。

本文:
 リリース日が近づいてきました。今作ってる Single Page Web App はもう一丁大きくなって、HTML Node の数は 20k 超えました。AngularJS の場合、もうなんかしないといけないサイズになりますが、React.js はサクサク動くだけです。この辺は DOM 操作とJavaScript 処理の差が出ました。
 ただ、React.js を使うだけで、Web App が速くなることではないと思います。やはり前書いたように AngularJS みたいに作り方次第です。AngularJS と同様、React.js Web App を速くする方法は必要なときだけ、データとDOMを更新して、また更新する必要な場所だけ更新します。
 AngularJS 1.0 の Dirty Check みたいに、React.js App を更新するには setState() という関数があります。this.setState({newState: newState}) を呼ぶと、その Component の render() 関数が呼ばれて、さらに、Component Tree の比較と行われます。そうすると、React.js は Virtual DOM の変化をキャッチして、実際の DOM を更新します。ただ、Component の render() 関数を呼ぶべきかどうかは shouldComponentUpdate() という関数がコントロールしています。デフォルトでは shouldComponentUpdate() は return true; のみです。この関数は必ず自分で書くべきです。または pureRenderMixin を使って、Component が更新必要なときだけ、true を返して、そうでない場合、false を返すべきです。
 さらに、Component は state を持つべきかどうかをよく考えないといけません。一般的に、Web App 全体の App Component に state を置いて、その他の Component は props のみ使うべきです。ただし、carousel や、toggle button みたいな自分の state が必要なとき、state を使いましょう。
 例えば、App の構造は:
    <div className="app">
        <panel1 data={this.state.data1} />
        <panel2 data={this.state.data2} />
    </div>

   App の state に data1, data2 が store から取得して、もし変化がある場合、this.setState({data1: newData}) を呼び出したら、panel1 は更新すべきですが、data2 が変わってないので、 panel2 はこの更新をスキップするために、panel2 component の shouldComponentUpdate 関数は
    return this.props.data2 !== nextProps.data2;
を書くべきです。もし data2 は複雑な object や、array の場合、deep check は難しいであれば、更新もそんなに頻繁ではない場合、いつも新しい object を作ってもいいようが気がします。
 そうでないと、React.js はいくら速くても、アプリの作りによる重くなることもありうるし、AngularJS より軽いですが、AngularJS みたい遅くなるケースもあります。