先週、つい React.js のバージョンを 0.12 から 15.3 にアップグレードしました。Component 自身の修正はそんなに多くないですが、jest でのテストはかなり変わりましたので、少し時間がかかりました。
まず、全ての node モジュールを最新バージョンにして、npm dedupe コマンドを実行して、node_modules のフォルダ構造をできるだけフラットにしました。これで、Windows でのファイル名が長すぎという git clone 時のエラーが少なくなるはずです。
そのあと、Webpack の Config を修正して、transpile できるようにします。こちらは主に、loader の変更です。新しい Babel では、stage や、destructuring などの ES2015 フェーチャーを個々のモジュールに分けたので、preset に ES2015とReactを設定しなければなりません。こちらは .babelrc に追加します。jest も Babel loader を使うので、rc ファイルに設定したほうがいいです。そのあと、使っている ES2015 のフェーチャーを plugin に追加します。主に、destructuring になります。
これで、transpile は通るようになります。ブラウザーで Web App をロードすると、いろんなエラーが出てきます。
− App を DOM に render するとき、ReactDOM.render を使います。
− getDOMNode() が削除されたので、ReactDOM.findDOMNode() に変更する必要があります。こちらは native の DOM Node div などを ref を入れると、自動的に DOM node の reference になるので、これはかなり便利になります。
− props は完全に readonly になるので、過去の悪いコードを直しないといけません。
− DOM node にサポートしない属性もエラーになりますので、こちらを props などの object から取り出して、設定する必要があります。例えば、contentKey とか自分で過去使っていた prop などに対して、 {...props} を使うところを { contentKey, ...others } = this.props をして、{...others} のみ React Component に渡します。
− React Component は owner が必要とかのエラーは複数の React.js バージョンが存在したからです。過去の Third Party lib 中の React.js バージョンをチェックした方がいいです。
− PureRenderMixin や classSet などの lib は React.js から分離されたため、個別に require する必要があります。
こちらのエラーを直すと、基本的に Web App は動きます。
一番大変なのは jest での Unit Tests の修正です。 setProps や、getDOMNOde() などもう使えないので、一括で findDOMNode() に変更したりして、動かない場所や、エラーになるところを一つ、一つ直す必要があります。こちらはほぼ一週間かかりました。(全部 1022 個 Unit test case)。
なお、airbnb から enzyme というライブラリーが出てきたので、こちらをこれから使う予定です。jest よりかなり使い易いです。
まぁ、全体的に見ると、そんなに手間をかかることではないので、早めにアップデートしたほうがいいかもしれません。
それでは。
Code
2016年9月18日日曜日
2016年9月10日土曜日
Web, mobile App と Restful API
最近、作ってるアプリは Restful API を採用されました。こちらは二つの Layer があります。Web Layer と App Layer。具体的に、App Layer はデータを保存したり、処理するだけです。Web Layer は実際の Web アプリやモバイルアプリにコミュニケーションします。
この二層 Layer は一見的に必要ないと思われてるかもしれません。ただ、API を使うとき、Web アプリとモバイルアプリの間に要求が違うときがあります。この場合、Web layer でその違いをカバーできます。さらに、JSON フォーマットの Data Contract は Web Layer で固めて、App Layer はほぼ自由に変更できるようになります。
例えば、Web の場合、通常ログイン状態は保持できないし、セッションタイムアウトもあります。モバイルの場合、基本的にログイン状態にあります。それで、フリーに見れるコンテンツとログインで見れるコンテンツをコントロールする必要があります。ただ、一旦ログインすると、もう扱うデータが同じになります。これは Web Layer と App Layer を分ける理由の一つです。
もう一つの理由としては、今 AWS や Azure などの Cloud プラットフォームが使い易くなります。App Layer は自分の会社に配置して、Web Layer はAWS などを使います。App Layer も他の内部のシステムにも利用できますし、データも基本的に自社にあるから、他の会社に保存して、漏れるとかの心配も少ないでしょう。
Web Layer と App Layer の間も基本的に HTTP Restful API になっています。Web Layer はアプリとの JSON Data Contract を定義して、DTO などは基本的に変更しにくいです。その後ろに存在する App Layer との間がデータのやり取りですので、変更は頻繁にあるかもしれません。アプリのバージョンが複数サポート必要があるとき、さらに使い易いです。
それでは。
この二層 Layer は一見的に必要ないと思われてるかもしれません。ただ、API を使うとき、Web アプリとモバイルアプリの間に要求が違うときがあります。この場合、Web layer でその違いをカバーできます。さらに、JSON フォーマットの Data Contract は Web Layer で固めて、App Layer はほぼ自由に変更できるようになります。
例えば、Web の場合、通常ログイン状態は保持できないし、セッションタイムアウトもあります。モバイルの場合、基本的にログイン状態にあります。それで、フリーに見れるコンテンツとログインで見れるコンテンツをコントロールする必要があります。ただ、一旦ログインすると、もう扱うデータが同じになります。これは Web Layer と App Layer を分ける理由の一つです。
もう一つの理由としては、今 AWS や Azure などの Cloud プラットフォームが使い易くなります。App Layer は自分の会社に配置して、Web Layer はAWS などを使います。App Layer も他の内部のシステムにも利用できますし、データも基本的に自社にあるから、他の会社に保存して、漏れるとかの心配も少ないでしょう。
Web Layer と App Layer の間も基本的に HTTP Restful API になっています。Web Layer はアプリとの JSON Data Contract を定義して、DTO などは基本的に変更しにくいです。その後ろに存在する App Layer との間がデータのやり取りですので、変更は頻繁にあるかもしれません。アプリのバージョンが複数サポート必要があるとき、さらに使い易いです。
それでは。
2016年8月28日日曜日
CSS Animation, Focus と Accessibility
先日、あるアコーディオン的な Form を作って、CSS で slide up / down アニメーションをつけました。実際 CSS animation を使うとき、要素の display: none から display: block に, visibility: visible から visibility: hidden を変更すると、アニメーションが実行されません。現在 CSS animation はある数字から、次の数字までしか動きません。Height の場合、0 から 200 までアニメートできますが、0 から auto まではできません。
では、なぜ jQuery animate 関数を使わないかと、CSS の方が効率がいいし、面白いからです。Chrome の FPS ツールでみると、CSS の場合、主に 50 FPS に一定していますが、jQuery animate は、JavaScript を実行しているので、FPS 一定ではないです。(https://greensock.com/js/speed.html)
それで、display: none -> block の代わりに、maxHeight: 0 -> 200px を css transition でアニメートするようにしました。maxHeight 0 となっても、実際その要素は DOM に存在していますので、Screen Reader は認識しています。さらに、そのエリアに Form <input /> があるため、Tab Stop も発生しています。
Focus が見えないところに行くのは大変良くないことです。ユーザーが Tab キーをおして、しばらく Focus はどこにあるかわからなくて、結構迷うようです。
では、css animation を使うとき、accessibility について実装方法としては幾つかコツがあります。
まず、<input /> みたいな Form 要素に対して、tabIndex を使って、 tab order を変更します。0 から -1 にします。0 の場合は、tab order をブラウザーを任していますが、-1 の場合、JavaScript の setFocus() 関数を使う必要があります。これで、ユーザーの Tab キーはその要素をスキップするようになります。
次、aria-hidden を使って、見えない部分を Screen Reader から隠します。aria-hidden を true にすれば、すべての Screen Reader が読まないようになります。
具体的に:
<form aria-hidden={isOpen ? false : true}>
<input tabIndex={isOpen ? 0 : -1} />
</form>
にするだけです。
これで、ユーザーの Tab Stop が Form Hidden の場合、そのエリアの <input /> などに行かないように、矢印キーを使っても、その部分が読まないようになります。アニメーションは maxHeight を使って、開いたり、閉じたりできます。
Accessibility について、いいコースがあります。英語ですけど。。。
https://www.udacity.com/course/web-accessibility--ud891
それでは。
では、なぜ jQuery animate 関数を使わないかと、CSS の方が効率がいいし、面白いからです。Chrome の FPS ツールでみると、CSS の場合、主に 50 FPS に一定していますが、jQuery animate は、JavaScript を実行しているので、FPS 一定ではないです。(https://greensock.com/js/speed.html)
それで、display: none -> block の代わりに、maxHeight: 0 -> 200px を css transition でアニメートするようにしました。maxHeight 0 となっても、実際その要素は DOM に存在していますので、Screen Reader は認識しています。さらに、そのエリアに Form <input /> があるため、Tab Stop も発生しています。
Focus が見えないところに行くのは大変良くないことです。ユーザーが Tab キーをおして、しばらく Focus はどこにあるかわからなくて、結構迷うようです。
では、css animation を使うとき、accessibility について実装方法としては幾つかコツがあります。
まず、<input /> みたいな Form 要素に対して、tabIndex を使って、 tab order を変更します。0 から -1 にします。0 の場合は、tab order をブラウザーを任していますが、-1 の場合、JavaScript の setFocus() 関数を使う必要があります。これで、ユーザーの Tab キーはその要素をスキップするようになります。
次、aria-hidden を使って、見えない部分を Screen Reader から隠します。aria-hidden を true にすれば、すべての Screen Reader が読まないようになります。
具体的に:
<form aria-hidden={isOpen ? false : true}>
<input tabIndex={isOpen ? 0 : -1} />
</form>
にするだけです。
これで、ユーザーの Tab Stop が Form Hidden の場合、そのエリアの <input /> などに行かないように、矢印キーを使っても、その部分が読まないようになります。アニメーションは maxHeight を使って、開いたり、閉じたりできます。
Accessibility について、いいコースがあります。英語ですけど。。。
https://www.udacity.com/course/web-accessibility--ud891
それでは。
2016年8月6日土曜日
React.js Component Wrapper
先週、pre-loaded データを使って、Component を render する代わりに、ajax を使って、データロードして、そのあと、 Component を render する機能が出てきました。この機能を実現するために、Wrapper Component を作ったら、簡単にできました。
具体的には autoLoadingComponent を作って、その中に、state を持たせて、componentDidMount の中に、ajax call を呼び出して、callback には setState() を読んで、state を更新します。この state をベースに元の Component に props として渡します。
そうすると、state が更新するたびに、元の Component が re-render されますので、auto load 機能が実現できます。簡単な例として:
autoLoader = React.createClass({
mixin: [pureRenderMixin],
getInitialState: function () {
return {
data: null,
loading: true
};
},
componentDidMount: function () {
// ajax call to get data
getJson(url, this.handleResponse);
},
handleResponse: function (response) {
this.setState({
data: response,
loading: false
});
},
render: function () {
if (this.state.loading) {
return <loading />;
} else {
return <component data={this.state.data} />;
}
}
});
これで、過去の Component も再利用できますし、どんなものでも auto load 機能がつけられます。
さらに、Redux を使ってる場合、Redux.connect() という関数があります。この関数も同じ考え方で、state を持ってる Wrapper を作って、過去の Component を自動更新できるようにします。その中も subscribe などの biolaplate コードもいっぱい入ってます。
それでは。
具体的には autoLoadingComponent を作って、その中に、state を持たせて、componentDidMount の中に、ajax call を呼び出して、callback には setState() を読んで、state を更新します。この state をベースに元の Component に props として渡します。
そうすると、state が更新するたびに、元の Component が re-render されますので、auto load 機能が実現できます。簡単な例として:
autoLoader = React.createClass({
mixin: [pureRenderMixin],
getInitialState: function () {
return {
data: null,
loading: true
};
},
componentDidMount: function () {
// ajax call to get data
getJson(url, this.handleResponse);
},
handleResponse: function (response) {
this.setState({
data: response,
loading: false
});
},
render: function () {
if (this.state.loading) {
return <loading />;
} else {
return <component data={this.state.data} />;
}
}
});
これで、過去の Component も再利用できますし、どんなものでも auto load 機能がつけられます。
さらに、Redux を使ってる場合、Redux.connect() という関数があります。この関数も同じ考え方で、state を持ってる Wrapper を作って、過去の Component を自動更新できるようにします。その中も subscribe などの biolaplate コードもいっぱい入ってます。
それでは。
2016年7月24日日曜日
React.js re-render で State のクリア方法
通常 React.js を使うと、コンポーネットを作って、DOM に render したら、たまには props を渡して、元の状態でもう一度 render したい時があります。ただ、state はコンポーネントを保持しているので、props が変更するだけで、re-render をしても、state が最初に戻らないのです。なぜなら、コンポーネントの getInitialState はコンポーネント最初の一回のみ実行されて、unmount を実行して、再 render しない限り、state は前の状態に保持されます。
内部の操作としては、props が変更したら、React.js はコンポーネントを DOM から消して、もう一度作るではなく、変更した部分だけ再度 render するだけです。同時に、ライフサイクルの関数を見てみると、 componentDidMount は一回だけ呼び出されて、そのあとは全て、componentDidUpdate が呼び出されます。
じゃ、どうやって、props 変更する際に state もクリアしますかと。基本的に二つ方法があります。
まずは、componentWillReceiveProps () 関数が毎回新しい props が渡されるとき呼び出されますので、その中で props に応じて、setState を呼び出して、リセットします。 それに、この関数の中で setState で state を変更しても、re-render が起こらないです。props の変更と state の変更が一回の re-render で更新されます。他のところで、 setState を呼び出すと、shouldcomponentUpdate から render、componentDidUpdate まで一通り実行されます。これは最善策です。
具体的に、getInitialState () で prepareInitialState を使って、新しい object を返します。親が re-render して、props が渡されたら、componentWillReceiveProps () 関数の中で、条件を判断して、もし state をクリアしたい場合、setState (prepareInitialState()) を呼び出すと、state が最初の状態に戻されます、props の変更とともに。
もう一つの方法は props と一緒に key を変更する。key をコンポーネントに設定すると、React.js の Virtual DOM 比較アウルゴリスムはコンポーネントのタイプ、key などの属性を最初に比較します。key が変更したことは、別のコンポーネントになるわけです。
ネットでは key を new Date() を設定して、親からの re-render があれば、変更されますので、いつもコンポーネントを最初から re-render します。
個人的には、方法1を使うべきです。React.js はすでに componentWillReceiveProps() 関数が提供されたので、それを使うべきです。方法2としては、確かに同じ目的が達成できるかもしれないが、実行されるコードが多いのです。それに、本来なら、re-render 必要ないかもしれないサイクルでも、key が変更されて、re-render になります。全体的に複雑になります。
それでは。
内部の操作としては、props が変更したら、React.js はコンポーネントを DOM から消して、もう一度作るではなく、変更した部分だけ再度 render するだけです。同時に、ライフサイクルの関数を見てみると、 componentDidMount は一回だけ呼び出されて、そのあとは全て、componentDidUpdate が呼び出されます。
じゃ、どうやって、props 変更する際に state もクリアしますかと。基本的に二つ方法があります。
まずは、componentWillReceiveProps () 関数が毎回新しい props が渡されるとき呼び出されますので、その中で props に応じて、setState を呼び出して、リセットします。 それに、この関数の中で setState で state を変更しても、re-render が起こらないです。props の変更と state の変更が一回の re-render で更新されます。他のところで、 setState を呼び出すと、shouldcomponentUpdate から render、componentDidUpdate まで一通り実行されます。これは最善策です。
具体的に、getInitialState () で prepareInitialState を使って、新しい object を返します。親が re-render して、props が渡されたら、componentWillReceiveProps () 関数の中で、条件を判断して、もし state をクリアしたい場合、setState (prepareInitialState()) を呼び出すと、state が最初の状態に戻されます、props の変更とともに。
もう一つの方法は props と一緒に key を変更する。key をコンポーネントに設定すると、React.js の Virtual DOM 比較アウルゴリスムはコンポーネントのタイプ、key などの属性を最初に比較します。key が変更したことは、別のコンポーネントになるわけです。
ネットでは key を new Date() を設定して、親からの re-render があれば、変更されますので、いつもコンポーネントを最初から re-render します。
個人的には、方法1を使うべきです。React.js はすでに componentWillReceiveProps() 関数が提供されたので、それを使うべきです。方法2としては、確かに同じ目的が達成できるかもしれないが、実行されるコードが多いのです。それに、本来なら、re-render 必要ないかもしれないサイクルでも、key が変更されて、re-render になります。全体的に複雑になります。
それでは。
2016年7月9日土曜日
iOS 9 HTML5 モーダル スクロールできない問題
TL; DR: iOS で modal を表示しても、body が相変わらずスクロールできるの修正方法:modal-open クラスに positive: relative を追加すれば、修正されます。
Bootstrap などを使うと、簡単に Modal ウィンドウが作れるようになります。基本的に CSS もあって、関数を呼び出すだけです。そのモーダルウィンドウは一般的に画面の真ん中に、スクロールできないようになっています。これは modal-open というクラスを body tag に設定することで、body の overflow を hidden に設定するだけでできています。
そうすると、モーダルウィンドウ自身はスクロールできるし、いつも画面の真ん中にあります。ユーザーに操作を提示しているため、最適していると思います。
iOS 9 がリリースされて、一つの問題は発生しました。body のスクロール禁止が効かなくなりました。
対応方法としては
modal-open { overflow: hidden; position: relative; }
にして、元と同じように動きます。PC ではこの問題が起きません。
他の修正方法としては、html, body と一緒に overflow: hidden を設定します。
それでは。
Bootstrap などを使うと、簡単に Modal ウィンドウが作れるようになります。基本的に CSS もあって、関数を呼び出すだけです。そのモーダルウィンドウは一般的に画面の真ん中に、スクロールできないようになっています。これは modal-open というクラスを body tag に設定することで、body の overflow を hidden に設定するだけでできています。
そうすると、モーダルウィンドウ自身はスクロールできるし、いつも画面の真ん中にあります。ユーザーに操作を提示しているため、最適していると思います。
iOS 9 がリリースされて、一つの問題は発生しました。body のスクロール禁止が効かなくなりました。
対応方法としては
modal-open { overflow: hidden; position: relative; }
にして、元と同じように動きます。PC ではこの問題が起きません。
他の修正方法としては、html, body と一緒に overflow: hidden を設定します。
それでは。
2016年6月18日土曜日
DOM は遅い?なぜ Virtual DOM を使うか
先日 DOM は遅いですか?なぜ Virtual DOM を使うかと聞かれました。DOM は遅いというか、DOM 操作が遅いです。ブラウザーには reflow, repaint などの処理があります。最初 HTML と CSS を分析して、ノードツリーをレンダリングした後に、JavaScript でダイナミックにノードの高さなどを変更したり、さらにノードを追加したりすると、ツリー中の他のノードを影響されるし、ツリーを再構築するかもしれません。それは当然遅いです。
実際 HTML のノードを遍歴する時や、ノードの属性を取得するときは遅いというか、むしろ速いです。これは jQuery 自分の event システムを持つ理由もなります。一つの要素をクリックすると、その event は buble して、親要素を遡って、ルートまで達して、event handler を呼び出します。もしこの中に DOM 操作がなければ、10 ms ぐらいで、できるかと思います。
ただし、もしノードの offsetHeight などの computed style を取得して、さらにその値をベースに、ノードの style.height を変更するときに、ブラウザーは大変な仕事になります。
通常ではブラウザーは変更をキャッシュして、ある時間、ある程度貯まったら、画面を一気に更新します。なぜなら、細かい更新をすると、main thread が止まったりするから、ユーザーには不親切です。が、offsetHeight などの属性を取得する時、ブラウザーはできるだけ正しい値を返すために、すべての更新を実行しなければなりません。次、style.height を変更すると、さらに、ツリーの中の他のノードの位置を計算します。この Read + Write が多くなると、main thread が応答しない場合もあります。
じゃ、どのように DOM 操作を早くできるかというと、いくつかの方法があります。
1 要素のスタイルを変更する場合は、width, height などを個別に変更するではなくて、新しい css class を作って、要素に入れると、こちらの更新が一つの reflow, repaint で済みます。
2 また、offsetHeight などの値取得する時や、getBoundingClientRect() 関数を呼び出す時、できるだけ、Read を完了したら、Write を行います。つまり、Read と Write を分けます。
3 DOM 操作を一つ一つするではなくて、まずキャッシュして、一気に行ったほうがいいです。例えば、documentFragment を使って、すべての要素を入れてから、その fragment を DOM に入れるとか。まず、要素の display を none にして(隠して)、その後に、height などを変更してから、display: block にして、再表示するとか。
他には、
4 アニメーションをするときに、opacityや、transform, scale など一つの layer になる css 属性を使いましょう。また、アニメーション対象の要素の position を absolute にするとか。
Virtual DOM を使う理由は上記の 3 と関係しています。JavaScript でサイトを操作するとき、Virtual DOM を使うと、変更がメモリーにキャッシュされます。すべての操作が終わったら、Virtual DOM から DOM に更新しに行きます。この場合、ブラウザーはすべての変更を一つの reflow, repaint でできるため、速いです。
今 React.js 以外、他に incremental dom、mithril などいろいろ JavaScript があります。まぁ、開発はコードを書くだけではなくて、テストなども考えなければなりませんので、今の時点で React.js は一番かなと思います。
それでは。
実際 HTML のノードを遍歴する時や、ノードの属性を取得するときは遅いというか、むしろ速いです。これは jQuery 自分の event システムを持つ理由もなります。一つの要素をクリックすると、その event は buble して、親要素を遡って、ルートまで達して、event handler を呼び出します。もしこの中に DOM 操作がなければ、10 ms ぐらいで、できるかと思います。
ただし、もしノードの offsetHeight などの computed style を取得して、さらにその値をベースに、ノードの style.height を変更するときに、ブラウザーは大変な仕事になります。
通常ではブラウザーは変更をキャッシュして、ある時間、ある程度貯まったら、画面を一気に更新します。なぜなら、細かい更新をすると、main thread が止まったりするから、ユーザーには不親切です。が、offsetHeight などの属性を取得する時、ブラウザーはできるだけ正しい値を返すために、すべての更新を実行しなければなりません。次、style.height を変更すると、さらに、ツリーの中の他のノードの位置を計算します。この Read + Write が多くなると、main thread が応答しない場合もあります。
じゃ、どのように DOM 操作を早くできるかというと、いくつかの方法があります。
1 要素のスタイルを変更する場合は、width, height などを個別に変更するではなくて、新しい css class を作って、要素に入れると、こちらの更新が一つの reflow, repaint で済みます。
2 また、offsetHeight などの値取得する時や、getBoundingClientRect() 関数を呼び出す時、できるだけ、Read を完了したら、Write を行います。つまり、Read と Write を分けます。
3 DOM 操作を一つ一つするではなくて、まずキャッシュして、一気に行ったほうがいいです。例えば、documentFragment を使って、すべての要素を入れてから、その fragment を DOM に入れるとか。まず、要素の display を none にして(隠して)、その後に、height などを変更してから、display: block にして、再表示するとか。
他には、
4 アニメーションをするときに、opacityや、transform, scale など一つの layer になる css 属性を使いましょう。また、アニメーション対象の要素の position を absolute にするとか。
Virtual DOM を使う理由は上記の 3 と関係しています。JavaScript でサイトを操作するとき、Virtual DOM を使うと、変更がメモリーにキャッシュされます。すべての操作が終わったら、Virtual DOM から DOM に更新しに行きます。この場合、ブラウザーはすべての変更を一つの reflow, repaint でできるため、速いです。
今 React.js 以外、他に incremental dom、mithril などいろいろ JavaScript があります。まぁ、開発はコードを書くだけではなくて、テストなども考えなければなりませんので、今の時点で React.js は一番かなと思います。
それでは。
登録:
投稿 (Atom)