先週、一つのバグを発見しました。まぁ、強いといえば、Back End の問題ですが、ただ引き起こされた原因は Front End にあります。
まず、SSO をするとき、Return Url を Back End に渡していますが、もし相対 Url の場合、正しく動きますが、フルの Url を渡したら、途中で処理を止まりました。原因は Back End の処理は絶対 Url の処理はできていません。
Domain は同じでも、Front End からどんな ReturnUrl 文字列が渡されるかはわからないので、ちゃんと処理しないと。。。
この件、<a href="/digital/url"> を書きましたが、実際 Return Url を取得するとき、e.target.href を使ってしまいました。この場合、Browser は相対 Url に Base Url を自動的に追加します。そうすると、本来なら、相対パスであるところは絶対パスになってしまいます。。。
解決方法は Back End 修正すべきですが、一応、 Front End も正しい動きにしたほうがいいと。e.target.getAttribute("href") にすれば、ちゃんと href 中の相対パスが取得できます。これは属性を直接使う場合、と getAttribute() 関数の違いです。
ちなみに、jQuery はいつも getAttribute を使っているそうです。
こちらは HTML Specification によると、.href を使う時は、DOM Property と呼んで、.getAttribute の場合は attribute となります。DOM Property は計算後の値になります。例えば、上記の .href。また .checked の場合、DOM Property なら true, false になりますた。.getAttribute を使うと、"" 空の文字列になる。今後ご注意を。
それでは。
Code
2016年10月16日日曜日
2016年10月3日月曜日
iOS での position: fixed をスムーズにする
先日リリースした Web App の Header を position: fixed にしました。PC では問題ないですが、iOS や Android など Tablet Webkit 系では、ちょっと変な動きをしました。実際指でスクロールすると、Header が一瞬消えて、そのあと画面がほぼ静止したとき、 fixed になります。これは多分 Tablet 自身のパフォーマンス問題で、解決方法を調べてました。
ネットではいろいろ書き込みがあります。基本的に
transform: translate3D(0, 0, 0) を使ったり、transform: translateZ(0) を親要素に追加して、強制的に Layer を作って、ハードウェアの加速を使います。
追加してみたら、ほぼ解決するようになりました。ただ、Header に要素が多い場合、まだまだ不自然です。デザイナーを相談して、できるだけ Header にある要素を Tablet の場合、減らしました。。。
さらに調べてみると、transform: translate3D(0, 0, 0) か translateZ(0) を使うと、Browser はその要素を一つの Layer として、イメージを作って、Video Card のメモリにアップロードします。そのあと、何かアニメーションが起きたら、CPU の時間ではなくて、Video カードを使って、画面を更新しています。これは速いはずです。
注意点としては、むやみにいろんな Layer を作ると、逆にパフォーマンスに悪影響が発生します。本当に使ったほうがいい時、使うよという書き込みもいっぱいあります。
もう一つの注意点としては、新しい Layer が作られたため、z-index と translateZ(0) が衝突する可能性があります。例えば、親の間に z-index を使って、画面上の上下位置を調整していますが、親レベルですので、translateZ(0) を使うと、その z-index が効かなくなる可能性があります。よくテストしましょう。
もう一つ、これは Tablet など画面の大きいデバイスでは、画面の小さい mobile phone では、position fixed を使わない方がいいです。media query を使って、header を元の位置に戻しましょう。
それでは。
ネットではいろいろ書き込みがあります。基本的に
transform: translate3D(0, 0, 0) を使ったり、transform: translateZ(0) を親要素に追加して、強制的に Layer を作って、ハードウェアの加速を使います。
追加してみたら、ほぼ解決するようになりました。ただ、Header に要素が多い場合、まだまだ不自然です。デザイナーを相談して、できるだけ Header にある要素を Tablet の場合、減らしました。。。
さらに調べてみると、transform: translate3D(0, 0, 0) か translateZ(0) を使うと、Browser はその要素を一つの Layer として、イメージを作って、Video Card のメモリにアップロードします。そのあと、何かアニメーションが起きたら、CPU の時間ではなくて、Video カードを使って、画面を更新しています。これは速いはずです。
注意点としては、むやみにいろんな Layer を作ると、逆にパフォーマンスに悪影響が発生します。本当に使ったほうがいい時、使うよという書き込みもいっぱいあります。
もう一つの注意点としては、新しい Layer が作られたため、z-index と translateZ(0) が衝突する可能性があります。例えば、親の間に z-index を使って、画面上の上下位置を調整していますが、親レベルですので、translateZ(0) を使うと、その z-index が効かなくなる可能性があります。よくテストしましょう。
もう一つ、これは Tablet など画面の大きいデバイスでは、画面の小さい mobile phone では、position fixed を使わない方がいいです。media query を使って、header を元の位置に戻しましょう。
それでは。
2016年9月18日日曜日
最新 React.js にアップデート
先週、つい 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 よりかなり使い易いです。
まぁ、全体的に見ると、そんなに手間をかかることではないので、早めにアップデートしたほうがいいかもしれません。
それでは。
まず、全ての 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 よりかなり使い易いです。
まぁ、全体的に見ると、そんなに手間をかかることではないので、早めにアップデートしたほうがいいかもしれません。
それでは。
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 になります。全体的に複雑になります。
それでは。
登録:
投稿 (Atom)